commit d4d82fa1d738924d270a81efd0b6948d39d48af7 Author: asterisk727 <59166650+asterisk727@users.noreply.github.com> Date: Mon Dec 29 19:42:54 2025 +0900 chore: initial commit diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..8039f6e --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,9 @@ +plugins: + - html +env: + browser: true + es2021: true +overrides: [] +parserOptions: + ecmaVersion: latest +rules: {} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..dfd0e30 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/.github/html5validator.yml b/.github/html5validator.yml new file mode 100644 index 0000000..9108940 --- /dev/null +++ b/.github/html5validator.yml @@ -0,0 +1,5 @@ +root: ./ +ignore_re: + - 'An "img" element must have an "alt" attribute, except under certain conditions' + - 'Element "div" not allowed as child of element "label" in this context' + - 'The "center" element is obsolete. Use CSS instead' # initialdzero diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..c498f2a --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,20 @@ +name: Check HTML/JS is valid + +on: + pull_request: + push: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Run HTML5 Validator + uses: Cyb3r-Jak3/html5validator-action@v7.2.0 + with: + config: ./.github/html5validator.yml + css: true + - name: Install modules for eslint + run: npm i + - name: Run ESLint + run: npx eslint . --ext .js,.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e50dbd6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +package-lock.json +/.vs diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..1330df7 --- /dev/null +++ b/.htaccess @@ -0,0 +1,5 @@ +# Over time, some patchers have changed path due to reorganisation and cleanup. +# This avoids link rot on mon's server by redirecting the old paths to the new ones + +Redirect permanent "/bemanipatcher/chuninewplus.html" "/bemanipatcher/chusannewplus.html" +Redirect permanent "/bemanipatcher/resort_anthem.html" "/bemanipatcher/resortanthem.html" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..56da3ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-202X two-torial Team & contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d32de0d --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# [TWO-TORIAL](https://two-torial.xyz)'s Web Patcher + +### URL: [https://patcher.two-torial.xyz/](https://patcher.two-torial.xyz/) + +## About + +This repo contains web patches for games incompatible with [spice2x](https://spice2x.github.io/). See our [spice2x patcher](https://github.com/two-torial/sp2xpatcher/) for those. +We'll be keeping the **repo public** and **url alive** for as long as possible, **feel free to [contibute](#Contributing)** for the community through pull requests! + +## Usage + +See our guide on [web patching](https://two-torial.xyz/extras/patchweb/) and use the url provided above. + +## Contributing + +### A new game + +- Add the new game html, it is easiest to copy an existing game and modify it. +- Modify the `` tag and the `<h1>` tag to the name of the new game. +- Modify the patcher for the new DLL names/patches. +- Keep indentation consistent. We will have to fix your PR if it contains poor formatting, which will delay the merging process. +- Modify `index.html` to add the new game. Sorting: alphabetical by game series, then in release order per game. +- Add a game image. 128x128px PNG files, please. Any blank space should be either white or transparent. +- Create a pull request. + +### Patches for an existing game + +- Open the game's html. +- If necessary: add a new PatchContainer. +- If necessary: add a new Patcher. +- Add your patches following the existing format. +- Create a pull request. diff --git a/SDGS125.html b/SDGS125.html new file mode 100644 index 0000000..e40b97c --- /dev/null +++ b/SDGS125.html @@ -0,0 +1,193 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8"> + <title>CHUNITHM Intl. SUN PLUS Modder + + + + + + +

CHUNITHM Intl. SUN PLUS Modder

+ + + diff --git a/SDGS130.html b/SDGS130.html new file mode 100644 index 0000000..5ddca1b --- /dev/null +++ b/SDGS130.html @@ -0,0 +1,153 @@ + + + + + + CHUNITHM Intl. LUMINOUS Modder + + + + + + +

CHUNITHM Intl. LUMINOUS Modder

+ + + diff --git a/amdaemon.html b/amdaemon.html new file mode 100644 index 0000000..55ac97c --- /dev/null +++ b/amdaemon.html @@ -0,0 +1,380 @@ + + + + + + amdaemon Modder + + + + + + +

amdaemon Modder

+ + + diff --git a/bishibashi.html b/bishibashi.html new file mode 100644 index 0000000..88a6055 --- /dev/null +++ b/bishibashi.html @@ -0,0 +1,26 @@ + + + + + THE ☆ BishiBashi DLL Modder + + + + + +

THE ☆ BishiBashi DLL Modder

+ + diff --git a/bombergirl.html b/bombergirl.html new file mode 100644 index 0000000..015dfa1 --- /dev/null +++ b/bombergirl.html @@ -0,0 +1,81 @@ + + + + + BomberGirl PC (eacloud) DLL Modder + + + + + +

BomberGirl DLL Modder

+ + diff --git a/chuni.html b/chuni.html new file mode 100644 index 0000000..78ce973 --- /dev/null +++ b/chuni.html @@ -0,0 +1,152 @@ + + + + + CHUNITHM Modder + + + + + + +

CHUNITHM Modder

+ + diff --git a/chuni_hx.html b/chuni_hx.html new file mode 100644 index 0000000..04f7892 --- /dev/null +++ b/chuni_hx.html @@ -0,0 +1,55 @@ + + + + + CHUNITHM PARADISE LOST (USA) Modder + + + + + +

CHUNITHM PARADISE LOST (USA) Modder

+ + + diff --git a/chuniair.html b/chuniair.html new file mode 100644 index 0000000..c8cfb0b --- /dev/null +++ b/chuniair.html @@ -0,0 +1,102 @@ + + + + + CHUNITHM AIR Modder + + + + + + + +

CHUNITHM AIR Modder

+ + diff --git a/chuniairplus.html b/chuniairplus.html new file mode 100644 index 0000000..f3c48fb --- /dev/null +++ b/chuniairplus.html @@ -0,0 +1,102 @@ + + + + + CHUNITHM AIR PLUS Modder + + + + + + + +

CHUNITHM AIR PLUS Modder

+ + diff --git a/chuniamazon.html b/chuniamazon.html new file mode 100644 index 0000000..4ea3a14 --- /dev/null +++ b/chuniamazon.html @@ -0,0 +1,113 @@ + + + + + CHUNITHM AMAZON Modder + + + + + +

CHUNITHM AMAZON Modder

+ + diff --git a/chuniamazonplus.html b/chuniamazonplus.html new file mode 100644 index 0000000..1971330 --- /dev/null +++ b/chuniamazonplus.html @@ -0,0 +1,113 @@ + + + + + CHUNITHM AMAZON PLUS Modder + + + + + +

CHUNITHM AMAZON PLUS Modder

+ + diff --git a/chunicrystal.html b/chunicrystal.html new file mode 100644 index 0000000..a9aa469 --- /dev/null +++ b/chunicrystal.html @@ -0,0 +1,98 @@ + + + + + CHUNITHM CRYSTAL Modder + + + + + +

CHUNITHM CRYSTAL Modder

+ + diff --git a/chunicrystalplus.html b/chunicrystalplus.html new file mode 100644 index 0000000..cbf219b --- /dev/null +++ b/chunicrystalplus.html @@ -0,0 +1,103 @@ + + + + + CHUNITHM CRYSTAL PLUS Modder + + + + + +

CHUNITHM CRYSTAL PLUS Modder

+ + diff --git a/chuniparadise.html b/chuniparadise.html new file mode 100644 index 0000000..990568f --- /dev/null +++ b/chuniparadise.html @@ -0,0 +1,115 @@ + + + + + CHUNITHM PARADISE (LOST) Modder + + + + + +

CHUNITHM PARADISE (LOST) Modder

+ + + diff --git a/chuniplus.html b/chuniplus.html new file mode 100644 index 0000000..fa442fc --- /dev/null +++ b/chuniplus.html @@ -0,0 +1,98 @@ + + + + + CHUNITHM PLUS Modder + + + + + + + +

CHUNITHM PLUS Modder

+ + diff --git a/chunistar.html b/chunistar.html new file mode 100644 index 0000000..781277c --- /dev/null +++ b/chunistar.html @@ -0,0 +1,103 @@ + + + + + CHUNITHM STAR Modder + + + + + + + + +

CHUNITHM STAR Modder

+ + diff --git a/chunistarplus.html b/chunistarplus.html new file mode 100644 index 0000000..28a3535 --- /dev/null +++ b/chunistarplus.html @@ -0,0 +1,104 @@ + + + + + CHUNITHM STAR PLUS Modder + + + + + + + + +

CHUNITHM STAR PLUS Modder

+ + diff --git a/chusan.html b/chusan.html new file mode 100644 index 0000000..2590163 --- /dev/null +++ b/chusan.html @@ -0,0 +1,88 @@ + + + + + CHUNITHM NEW!! Modder + + + + + +

CHUNITHM NEW!! Modder

+ + diff --git a/chusan_hj.html b/chusan_hj.html new file mode 100644 index 0000000..42a2b39 --- /dev/null +++ b/chusan_hj.html @@ -0,0 +1,62 @@ + + + + + 中二节奏 NEW!! Modder + + + + + +

中二节奏 NEW!! Modder

+ + diff --git a/chusanluminous.html b/chusanluminous.html new file mode 100644 index 0000000..3bae434 --- /dev/null +++ b/chusanluminous.html @@ -0,0 +1,381 @@ + + + + + + CHUNITHM LUMINOUS Modder + + + + + + +

CHUNITHM LUMINOUS Modder

+ + + diff --git a/chusanluminousplus.html b/chusanluminousplus.html new file mode 100644 index 0000000..bd054d3 --- /dev/null +++ b/chusanluminousplus.html @@ -0,0 +1,205 @@ + + + + + + CHUNITHM LUMINOUS PLUS Modder + + + + + + +

CHUNITHM LUMINOUS PLUS Modder

+ + + diff --git a/chusannew.html b/chusannew.html new file mode 100644 index 0000000..ba27d6e --- /dev/null +++ b/chusannew.html @@ -0,0 +1,187 @@ + + + + + + CHUNITHM NEW!! Modder + + + + + + +

CHUNITHM NEW!! Modder

+ + + diff --git a/chusannewplus.html b/chusannewplus.html new file mode 100644 index 0000000..e2577f2 --- /dev/null +++ b/chusannewplus.html @@ -0,0 +1,202 @@ + + + + + CHUNITHM NEW PLUS!! Modder + + + + + +

CHUNITHM NEW PLUS!! Modder

+ + diff --git a/chusansun.html b/chusansun.html new file mode 100644 index 0000000..fbb8a8f --- /dev/null +++ b/chusansun.html @@ -0,0 +1,197 @@ + + + + + + CHUNITHM SUN Modder + + + + + + +

CHUNITHM SUN Modder

+ + + diff --git a/chusansunplus.html b/chusansunplus.html new file mode 100644 index 0000000..861aa04 --- /dev/null +++ b/chusansunplus.html @@ -0,0 +1,197 @@ + + + + + + CHUNITHM SUN PLUS Modder + + + + + + +

CHUNITHM SUN PLUS Modder

+ + + diff --git a/chusanverse.html b/chusanverse.html new file mode 100644 index 0000000..21dc265 --- /dev/null +++ b/chusanverse.html @@ -0,0 +1,366 @@ + + + + + + CHUNITHM VERSE Modder + + + + + + +

CHUNITHM VERSE Modder

+ + + diff --git a/chusanxverse.html b/chusanxverse.html new file mode 100644 index 0000000..2631b5b --- /dev/null +++ b/chusanxverse.html @@ -0,0 +1,184 @@ + + + + + + CHUNITHM XVERSE Modder + + + + + + +

CHUNITHM XVERSE Modder

+ + + diff --git a/css/file.eot b/css/file.eot new file mode 100644 index 0000000..32d36be Binary files /dev/null and b/css/file.eot differ diff --git a/css/file.svg b/css/file.svg new file mode 100644 index 0000000..55f885b --- /dev/null +++ b/css/file.svg @@ -0,0 +1,11 @@ + + + +Generated by IcoMoon + + + + + + + \ No newline at end of file diff --git a/css/file.ttf b/css/file.ttf new file mode 100644 index 0000000..805576d Binary files /dev/null and b/css/file.ttf differ diff --git a/css/file.woff b/css/file.woff new file mode 100644 index 0000000..c837467 Binary files /dev/null and b/css/file.woff differ diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..ce3e550 --- /dev/null +++ b/css/style.css @@ -0,0 +1,446 @@ +@font-face { + font-family: "file"; + src: url("file.eot?abyff3"); + src: url("file.eot?abyff3#iefix") format("embedded-opentype"), + url("file.ttf?abyff3") format("truetype"), + url("file.woff?abyff3") format("woff"), + url("file.svg?abyff3#file") format("svg"); + font-weight: normal; + font-style: normal; +} +.tagline { + font-family: monospace; +} + +.icons, +.subsection { + display: grid; + grid-template-columns: repeat(auto-fit, 180px); + grid-auto-flow: dense; + align-items: stretch; + margin: 0 auto; + text-align: center; + justify-content: center; +} + +.subsection { + grid-column: 1 / -1; + margin: unset; + /* I don't understand anything */ + margin-right: -15px; + padding-right: 15px; +} + +.gameicon, +.patchContainer { + border-radius: 2px; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + color: inherit; + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); + background: #fff; +} + +.gameicon { + display: flex; + flex-direction: column; + text-decoration: none; + margin: 15px; + width: 144px; + padding: 10px; + justify-content: center; +} + +label.gameicon { + cursor: pointer; +} + +input.sectionToggle, +input.sectionToggle + div { + display: none; +} + +input:checked.sectionToggle + div { + display: grid; +} + +.gameicon:hover, +.dragover { + box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22); +} + +.gameicon img { + margin-bottom: 10px; + border-radius: 2px; + width: 128px; + height: 128px; + box-shadow: 0 2px 1px rgba(0, 0, 0, 0.24), 0 0px 1px rgba(0, 0, 0, 0.48); + align-self: center; + margin-top: 5px; +} + +.gameicon > div { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; +} + +.fileInput { + display: none; +} + +.fileLabel { + cursor: pointer; +} + +.success { + text-align: center; + border-radius: 10px; +} + +.success.hidden { + display: none; +} + +.patchContainer { + padding: 20px; + max-width: 650px; + margin: 0 auto 2em; + position: relative; +} + +.tooltip { + visibility: hidden; + font-size: 14px; + margin-left: 8px; + padding: 8px; + border-radius: 4px; + position: relative; + white-space: nowrap; + cursor: pointer; +} + +.tooltip:hover, +.tooltip:focus, +.tooltip:active { + visibility: visible; + color: white; + border: none; + margin-top: 0px; + position: absolute; + padding: 3px 16px; + white-space: normal; + max-width: 300px; + z-index: 11; +} + +.tooltip:before { + visibility: visible; + content: "?"; + font-size: 18px; + margin-right: 8px; + border-radius: 50%; + padding: 2px 9px; + margin-left: -8px; + color: white; + cursor: pointer; + z-index: 10; +} + +.tooltip:hover:before, +.tooltip:focus:before, +.tooltip:active:before { + color: black; + display: none; +} + +.danger:before { + content: "!!"; +} + +body { + margin: 40px auto; + max-width: 1300px; + line-height: 1.6; + font-size: 18px; + color: #000; + padding: 0 10px; + background: #24273a; + font-family: "Source Sans Pro", Helvetica, Arial, sans-serif; +} + +h1, +h2, +h3 { + line-height: 1.2; + text-align: center; + color: inherit; + text-decoration: inherit; +} + +h4, +h5 { + line-height: 1; + margin: 0px auto; +} + +button, +.fileLabel > strong { + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0.25, 0.5, 0.5, 1); + position: relative; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 700; + letter-spacing: 0.04em; + transition-property: box-shadow, background; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25), 0px 0px 2px rgba(0, 0, 0, 0.125); + cursor: pointer; +} + +.fileLabel > strong { + line-height: 3rem; +} + +button:disabled { + background-color: rgba(0, 0, 0, 0.12); + color: rgba(0, 0, 0, 0.38); + box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.2), + 0px 0px 0px 0px rgba(0, 0, 0, 0.14), 0px 0px 0px 0px rgba(0, 0, 0, 0.12); + cursor: default; +} + +button:hover:enabled, +.fileLabel > strong:hover { + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.25), 0px 0px 4px rgba(0, 0, 0, 0.125); +} + +.matchPercent { + font-size: 15px; + font-style: italic; +} + +.matchSuccess { + font-size: 15px; + font-style: italic; +} + +li > button { + height: 24px; + padding: 0 7px; + line-height: 0; +} + +.patches { + margin: 1em auto; +} + +input[type="checkbox"], +input[type="radio"] { + vertical-align: middle; + position: relative; + bottom: 1px; +} + +input[type="radio"] { + bottom: 2px; +} + +.patches label { + margin-left: 4px; +} + +.dragover > * { + filter: blur(5px); +} + +.dragover::before { + content: ""; + display: block; + height: 100%; + position: absolute; + top: 0; + left: 0; + width: 100%; + text-align: center; + z-index: 10; + outline: dashed 10px; +} + +.dragover::after { + content: "\e900"; + font-family: "file" !important; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + display: block; + height: auto; + position: absolute; + top: calc(50% - 3rem); + left: 0; + width: 100%; + text-align: center; + z-index: 11; + font-size: 8rem; +} + +.tooltip:not(:hover) { + font-size: 0px; + padding: 0; + margin-left: 16px; +} + +.tooltip { + visibility: hidden; + font-size: 14px; + margin-left: 8px; + padding: 8px; + border-radius: 4px; + position: relative; + background: gray; + white-space: nowrap; + cursor: pointer; + display: inline-block; +} +.patchPreviewLabel { + cursor: pointer; +} +.patchPreviewToggle { + display: none; +} +.patchPreview { + display: none; +} +input[type="checkbox"]:checked + .patchPreview { + display: block; +} +input[type="checkbox"] ~ ul > li.patch-off { + display: none; +} +input[type="checkbox"] ~ ul > li.patch-on { + display: list-item; +} +input[type="checkbox"]:checked ~ ul > li.patch-on { + display: none; +} +input[type="checkbox"]:checked ~ ul > li.patch-off { + display: list-item; +} + +/* Palette: Catppuccin Macchiato */ +@media (prefers-color-scheme: dark) { + body { + background: #24273a; + color: #cad3f5; + } + a { + color: #c6a0f6; + } + .gameicon, + .patchContainer { + background-color: #181926; + } + label.gameicon { + background: #181926; + } + .subsection { + background-color: #363a4f; + } + .matchPercent, + .error { + color: #ed8796; + } + .matchSuccess { + color: #a6da95; + } + .success { + background-color: #a6da95; + color: #000; + } + button, + .fileLabel > strong { + background-color: #c6a0f6; + color: #000; + } + button:disabled { + background-color: #ffffff05; + color: #ffffff55; + } + .tooltip, + .tooltip:before { + background-color: #8087a2; + } + .danger, + .danger:before { + background-color: #f5a97f !important; + } +} + +/* Palette: Catppuccin Latte */ +@media (prefers-color-scheme: light) { + body { + background: #eff1f5; + color: #4c4f69; + } + a { + color: #8839ef; + } + .gameicon, + .patchContainer { + background-color: #dce0e8; + } + label.gameicon { + background: #dce0e8; + } + .subsection { + background-color: #ccd0da; + } + .matchPercent, + .error { + color: #d20f39; + } + .matchSuccess { + color: #40a02b; + } + .success { + background-color: #40a02b; + color: #fff; + } + button, + .fileLabel > strong { + background-color: #8839ef; + color: #fff; + } + button:disabled { + background-color: rgba(0, 0, 0, 0.12); + color: rgba(0, 0, 0, 0.38); + } + .tooltip, + .tooltip:before { + background-color: #9ca0b0; + } + .danger, + .danger:before { + background-color: #fe640b; + } +} +.image-wrapper { + position: relative; +} +.image-hover { + position: absolute; + top: 0; + left: 1; + opacity: 0; + transition: opacity 0.5s ease-out; +} +.image-hover:hover { + opacity: 1; +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2c1872d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,8 @@ +# What is this folder? + +Did you spend ages getting a hex edit working? Do you want to document your +process somewhere so you don't forget? + +Perhaps you found a really cool way to find new timer-freeze patches for all +games, or some audio trick that helps identify stuff. Make a .md file for your +game or technique and put it here so the knowledge is not lost. diff --git a/docs/chuni_offset_finding.md b/docs/chuni_offset_finding.md new file mode 100644 index 0000000..0c308d0 --- /dev/null +++ b/docs/chuni_offset_finding.md @@ -0,0 +1,53 @@ +# Notes on updating offsets for Crystal Plus + +Windows 7 patch is missing. + +Steps to determine these values: + +1. Take old values from crystal +1. Use IDA's "Jump" -> "Jump to file offset..." feature to convert the file addresses to virtual memory addresses +1. Take chunithm crystal plus executable +1. Use virtual memory addresses from crystal, same spot is likely close to old one +1. Use some more instructions/bytes around the offsets from crystal to find same patterns on crystal plus +1. Check if disassembly area is identical regarding the change +1. At the bottom left of the disassembly text view, you see something like this: `00905500 | 00D06100: sub_D06100 (Synchronized with Hex View-1)`. The first address is the raw file address, the second one is the virtual address when loaded into memory +1. Take the first address once you found the exact same spot in chunithm crystal + +Format below for addresses: crystal file address -> crystal virtual address -> crystal plus virtual address -> crystal plus file address + +``` +Allow 127.0.0.1/localhost as the network server", +0x905500 -> 00D06100 -> 00D97F80 -> 00997380 +0x16CE988 -> 01AD0388 -> 01B17E80 -> 01717080 + +"Disable shop close lockout", +0x92D153 -> 00D2DD53 -> 00DBF6D3 -> 009BEAD3 + +"Force shared audio mode, system audio samplerate must be 48000", +0xCD470A -> 010D530A -> 010F26BA -> 00CF1ABA + +"Force 2 channel audio output", +0xCD47E1 -> 010D53E1 -> 010F2791 -> 00CF1B91 + +"Patch for Windows 7", +0x1A404E8 -> jump failed +0x1A404F1 -> jump failed + +"Disable Song Select Timer", +0x748FE2 -> 00B49BE2 -> 00B669C2 -> 00765DC2 + +"Set All Timers to 999", +0x5FC300 -> 009FCF00 -> 00A14870 -> 00613C70 + +"Better patch for head-to-head play", +0x464DB3 -> 008659B3 -> 00879A03 -> 00478E03 + +Increase max credits to 254", +0xC204B7 -> 010210B7 -> 01094767 -> 00C93B67 + +"Free Play", +0xC208D5 -> 010214D5 -> 01094B85 -> 00C93F85 + +"Dummy LED", +0x2499D7 -> 0064A5D7 -> 0064A5D7 -> 002499D7 +``` diff --git a/docs/why_no_n_minus_zero.md b/docs/why_no_n_minus_zero.md new file mode 100644 index 0000000..6889dfd --- /dev/null +++ b/docs/why_no_n_minus_zero.md @@ -0,0 +1,81 @@ +# What does n-0 mean? +"n-0" is part of a common nomenclature in the arcade scene referring to +freshness of data. "n-0" (n minus zero) is the current game release, like a +mathematical equation. "n-1" is the previous game release. Rarely you will see +"n+1" where the next, unreleased version of a game is leaked early. + +A time can also be added to specify lengths instead of versions. For example, +"n-6 months" or "n-1 year". + +# Why no n-0 in Bemanipatcher? + +The original policy of this repo was to accept any patch set that looked sane +and had consistent style, to be a "global" repository for all games that had +been released. Sometimes some private release would have hex edits appear as a +PR, so I accepted them because I didn't want to get into the drama of "why does +he have data but I don't". + +In mid 2021, some groups decided they'd start leaking the latest updates for +pretty much every game under the sun. In addition to being a hugely +irresponsible idea (Sega is especially litigious), this produced a lot of noise +in PRs from people hastily trying to port edits. + +I have had several terrible PRs submitted that I have had to revert. These +include: +- Broken patches +- Misattributed sources +- Totally-fine patch sets where I immediately received a Discord DM saying "I + didn't want that to be public, delete it please" +- PRs opened and closed almost immediately by someone reconsidering +- PRs opened, merged and then reverted by the original uploader + +As such, this repo now has a new rule: No n-0, unless it is more than a year +old. + +# When is n-0 OK? Why is day-old n-1 OK? +In addition to the obvious frustration I have with all these junk PRs, read on +for some moral soapboxing as to why you should reconsider public n-0. + +**Remember: this is my own opinion.** I like to think it's a fairly common one. You are welcome to disagree with me, but it won't change the policy. + +Why the year rule? When a new game comes out, why is the n-1 from yesterday +suddenly OK? There are two important aspects to my reasoning: Acting in good +faith, and preservation. + +You have to remember that rhythm games are an extremely niche market when +compared to all other game formats. Arcade rhythm games are even more niche, and +critically, comprise a different payment model (credits vs gacha/one-time-buy). + +Compared to huge MOBAs and FPS games where the majority just pays and plays the +game, the at-home-arcade community *is* big enough to cause financial problems +if they all stay at home, which is why we've seen COVID hit Sega hard enough to +start closing flagship arcades in Akihabara. + +I have heard firsthand from arcade operators that as soon as home-data is +released that matches or exceeds the version in their arcade, attendance drops +sharply. After all, with no commute and no credits, playing at home on your ASC +is usually good enough. + +So the first point, acting in good faith. By releasing old data, you minimise +depriving arcade operators of their income. Whether it's in Japan and the coins +go straight to Konami, or you're in Europe and they fund your local arcade's +next cab purchase, it's critical to actually financially support the games in +this niche. + +Adding the 1 year specifier is for games like Jubeat and Reflecbeat - games that +have not seen a new release in years. I believe it's acting in good faith to +release year old data. If there's been literally no updates in a year... That's +on Konami for abandoning a property. + +The second aspect is preservation. Except for buying an offline cab and hoping +it has the right version (good luck), there is simply no way to legitimately +play old releases of these games. This is why n-1 suddenly becomes "good" once +the next version of the game is released. So much digital history has been lost +due to bitrot and always-online games becoming unplayable. I like the idea of +being able to play every game in a series ever released. + +# Why did you write all this? +A lot of people who share my opinion like to loudly flame newcomers thirsty for +the latest data, chastising them for not innately knowing the correct etiquette. +This is my attempt to explain where my opinions come from. If it's useful to +even 1 person, it's done its job. diff --git a/favicon/android-chrome-192x192.png b/favicon/android-chrome-192x192.png new file mode 100644 index 0000000..0336bfa Binary files /dev/null and b/favicon/android-chrome-192x192.png differ diff --git a/favicon/android-chrome-512x512.png b/favicon/android-chrome-512x512.png new file mode 100644 index 0000000..2c86b96 Binary files /dev/null and b/favicon/android-chrome-512x512.png differ diff --git a/favicon/apple-touch-icon.png b/favicon/apple-touch-icon.png new file mode 100644 index 0000000..d493e65 Binary files /dev/null and b/favicon/apple-touch-icon.png differ diff --git a/favicon/favicon-16x16.png b/favicon/favicon-16x16.png new file mode 100644 index 0000000..c65faf4 Binary files /dev/null and b/favicon/favicon-16x16.png differ diff --git a/favicon/favicon-32x32.png b/favicon/favicon-32x32.png new file mode 100644 index 0000000..b73ab2d Binary files /dev/null and b/favicon/favicon-32x32.png differ diff --git a/favicon/favicon.ico b/favicon/favicon.ico new file mode 100644 index 0000000..bc842fa Binary files /dev/null and b/favicon/favicon.ico differ diff --git a/favicon/site.webmanifest b/favicon/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/favicon/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/futuretone.html b/futuretone.html new file mode 100644 index 0000000..cde614a --- /dev/null +++ b/futuretone.html @@ -0,0 +1,146 @@ + + + + + Hatsune Miku: Project DIVA Arcade Future Tone Modder + + + + + +

Hatsune Miku: Project DIVA Arcade Future Tone Modder

+ + + diff --git a/gc4ex.html b/gc4ex.html new file mode 100644 index 0000000..da75231 --- /dev/null +++ b/gc4ex.html @@ -0,0 +1,103 @@ + + + + + Groove Coaster 4EX Modder + + + + + +

Groove Coaster 4EX Modder

+ + diff --git a/gfdmv4.html b/gfdmv4.html new file mode 100644 index 0000000..d2da650 --- /dev/null +++ b/gfdmv4.html @@ -0,0 +1,33 @@ + + + + + GFDM V4 RockxRock DLL Modder + + + + + +

GFDM V4 RockxRock DLL Modder

+ + diff --git a/gfdmv5.html b/gfdmv5.html new file mode 100644 index 0000000..8221ce2 --- /dev/null +++ b/gfdmv5.html @@ -0,0 +1,50 @@ + + + + + GFDM V5 Rock to Infinity DLL Modder + + + + + +

GFDM V5 Rock to Infinity DLL Modder

+ + diff --git a/gfdmv6.html b/gfdmv6.html new file mode 100644 index 0000000..fd36b90 --- /dev/null +++ b/gfdmv6.html @@ -0,0 +1,44 @@ + + + + + GFDM V6 BLAZING!!!! DLL Modder + + + + + +

GFDM V6 BLAZING!!!! DLL Modder

+ + diff --git a/gfdmv7.html b/gfdmv7.html new file mode 100644 index 0000000..1505b9f --- /dev/null +++ b/gfdmv7.html @@ -0,0 +1,50 @@ + + + + + GFDM V7 DLL Modder + + + + + +

GFDM V7 DLL Modder

+ + diff --git a/gfdmv8.html b/gfdmv8.html new file mode 100644 index 0000000..8c33a03 --- /dev/null +++ b/gfdmv8.html @@ -0,0 +1,50 @@ + + + + + GFDM V8 DLL Modder + + + + + +

GFDM V8 DLL Modder

+ + diff --git a/gfdmxg2.html b/gfdmxg2.html new file mode 100644 index 0000000..d57e525 --- /dev/null +++ b/gfdmxg2.html @@ -0,0 +1,34 @@ + + + + + GFDM XG2 DLL Modder + + + + + +

GFDM XG2 DLL Modder

+ + diff --git a/gfdmxg3.html b/gfdmxg3.html new file mode 100644 index 0000000..63a888d --- /dev/null +++ b/gfdmxg3.html @@ -0,0 +1,25 @@ + + + + + GFDM XG3 DLL Modder + + + + + +

GFDM XG3 DLL Modder

+ + diff --git a/img/aaa_chuni.png b/img/aaa_chuni.png new file mode 100644 index 0000000..27a570e Binary files /dev/null and b/img/aaa_chuni.png differ diff --git a/img/bishibashi.png b/img/bishibashi.png new file mode 100644 index 0000000..5ef311e Binary files /dev/null and b/img/bishibashi.png differ diff --git a/img/bombergirl.png b/img/bombergirl.png new file mode 100644 index 0000000..2bcb534 Binary files /dev/null and b/img/bombergirl.png differ diff --git a/img/chu/ni01.png b/img/chu/ni01.png new file mode 100644 index 0000000..fa6edbd Binary files /dev/null and b/img/chu/ni01.png differ diff --git a/img/chu/ni02plus.png b/img/chu/ni02plus.png new file mode 100644 index 0000000..8cfd537 Binary files /dev/null and b/img/chu/ni02plus.png differ diff --git a/img/chu/ni03air.png b/img/chu/ni03air.png new file mode 100644 index 0000000..76803dd Binary files /dev/null and b/img/chu/ni03air.png differ diff --git a/img/chu/ni04airplus.png b/img/chu/ni04airplus.png new file mode 100644 index 0000000..5a8cb14 Binary files /dev/null and b/img/chu/ni04airplus.png differ diff --git a/img/chu/ni05star.png b/img/chu/ni05star.png new file mode 100644 index 0000000..48cf54a Binary files /dev/null and b/img/chu/ni05star.png differ diff --git a/img/chu/ni06starplus.png b/img/chu/ni06starplus.png new file mode 100644 index 0000000..e58fb17 Binary files /dev/null and b/img/chu/ni06starplus.png differ diff --git a/img/chu/ni07amazon.png b/img/chu/ni07amazon.png new file mode 100644 index 0000000..d0f21f5 Binary files /dev/null and b/img/chu/ni07amazon.png differ diff --git a/img/chu/ni08amazonplus.png b/img/chu/ni08amazonplus.png new file mode 100644 index 0000000..93def86 Binary files /dev/null and b/img/chu/ni08amazonplus.png differ diff --git a/img/chu/ni09crystal.png b/img/chu/ni09crystal.png new file mode 100644 index 0000000..f937785 Binary files /dev/null and b/img/chu/ni09crystal.png differ diff --git a/img/chu/ni10crystalplus.png b/img/chu/ni10crystalplus.png new file mode 100644 index 0000000..9dc2051 Binary files /dev/null and b/img/chu/ni10crystalplus.png differ diff --git a/img/chu/ni11paradise.png b/img/chu/ni11paradise.png new file mode 100644 index 0000000..d6662af Binary files /dev/null and b/img/chu/ni11paradise.png differ diff --git a/img/chu/ni11paradiselost.png b/img/chu/ni11paradiselost.png new file mode 100644 index 0000000..a4e0a71 Binary files /dev/null and b/img/chu/ni11paradiselost.png differ diff --git a/img/chu/san12.png b/img/chu/san12.png new file mode 100644 index 0000000..b9e6d2e Binary files /dev/null and b/img/chu/san12.png differ diff --git a/img/chu/san13newplus.png b/img/chu/san13newplus.png new file mode 100644 index 0000000..f216144 Binary files /dev/null and b/img/chu/san13newplus.png differ diff --git a/img/chu/san14sun.png b/img/chu/san14sun.png new file mode 100644 index 0000000..f29ab87 Binary files /dev/null and b/img/chu/san14sun.png differ diff --git a/img/chu/san15sunplus.png b/img/chu/san15sunplus.png new file mode 100644 index 0000000..f9e0936 Binary files /dev/null and b/img/chu/san15sunplus.png differ diff --git a/img/chu/san16luminous.png b/img/chu/san16luminous.png new file mode 100644 index 0000000..f5227d0 Binary files /dev/null and b/img/chu/san16luminous.png differ diff --git a/img/chu/san17luminousplus.png b/img/chu/san17luminousplus.png new file mode 100644 index 0000000..7689119 Binary files /dev/null and b/img/chu/san17luminousplus.png differ diff --git a/img/chu/san18verse.png b/img/chu/san18verse.png new file mode 100644 index 0000000..21a4559 Binary files /dev/null and b/img/chu/san18verse.png differ diff --git a/img/chu/san19xverse.png b/img/chu/san19xverse.png new file mode 100644 index 0000000..eeb38cc Binary files /dev/null and b/img/chu/san19xverse.png differ diff --git a/img/chu/sanhj1.png b/img/chu/sanhj1.png new file mode 100644 index 0000000..3c76fdc Binary files /dev/null and b/img/chu/sanhj1.png differ diff --git a/img/futuretone.png b/img/futuretone.png new file mode 100644 index 0000000..a43a8f5 Binary files /dev/null and b/img/futuretone.png differ diff --git a/img/gc4ex.png b/img/gc4ex.png new file mode 100644 index 0000000..0a82fc7 Binary files /dev/null and b/img/gc4ex.png differ diff --git a/img/gdfm/gfdmv4.png b/img/gdfm/gfdmv4.png new file mode 100644 index 0000000..cdba063 Binary files /dev/null and b/img/gdfm/gfdmv4.png differ diff --git a/img/gdfm/gfdmv5.png b/img/gdfm/gfdmv5.png new file mode 100644 index 0000000..cd91396 Binary files /dev/null and b/img/gdfm/gfdmv5.png differ diff --git a/img/gdfm/gfdmv6.png b/img/gdfm/gfdmv6.png new file mode 100644 index 0000000..8c808d1 Binary files /dev/null and b/img/gdfm/gfdmv6.png differ diff --git a/img/gdfm/gfdmv7.png b/img/gdfm/gfdmv7.png new file mode 100644 index 0000000..ed71ae4 Binary files /dev/null and b/img/gdfm/gfdmv7.png differ diff --git a/img/gdfm/gfdmv8.png b/img/gdfm/gfdmv8.png new file mode 100644 index 0000000..d61a8b0 Binary files /dev/null and b/img/gdfm/gfdmv8.png differ diff --git a/img/gdfm/gfdmxg2.png b/img/gdfm/gfdmxg2.png new file mode 100644 index 0000000..e1b47bb Binary files /dev/null and b/img/gdfm/gfdmxg2.png differ diff --git a/img/gdfm/gfdmxg3.png b/img/gdfm/gfdmxg3.png new file mode 100644 index 0000000..b5faa66 Binary files /dev/null and b/img/gdfm/gfdmxg3.png differ diff --git a/img/idz.png b/img/idz.png new file mode 100644 index 0000000..78d7fb3 Binary files /dev/null and b/img/idz.png differ diff --git a/img/kancolle.png b/img/kancolle.png new file mode 100644 index 0000000..c4f80d1 Binary files /dev/null and b/img/kancolle.png differ diff --git a/img/taikonotatsujin.png b/img/taikonotatsujin.png new file mode 100644 index 0000000..5ea54c0 Binary files /dev/null and b/img/taikonotatsujin.png differ diff --git a/img/wacca4reverse.png b/img/wacca4reverse.png new file mode 100644 index 0000000..a1c84dc Binary files /dev/null and b/img/wacca4reverse.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..06d08f5 --- /dev/null +++ b/index.html @@ -0,0 +1,173 @@ + + + + + TWO-TORIAL's Web Patcher + + + + +

TWO-TORIAL's Web Patcher

+

By the community, for the community!

+
+ + +
THE ☆ BishiBashi
+
+ + + +
BomberGirl PC (eacloud)
+
+ + + +
+ + +
CHUNITHM
+
+ + +
CHUNITHM PLUS
+
+ + +
CHUNITHM AIR
+
+ + +
CHUNITHM AIR PLUS
+
+ + +
CHUNITHM STAR
+
+ + +
CHUNITHM STAR PLUS
+
+ + +
CHUNITHM AMAZON
+
+ + +
CHUNITHM AMAZON PLUS
+
+ + +
CHUNITHM CRYSTAL
+
+ + +
CHUNITHM CRYSTAL PLUS
+
+ +
+ + +
+
CHUNITHM PARADISE (LOST)
+
+ + +
CHUNITHM PARADISE LOST (USA)
+
+ + +
CHUNITHM NEW!!
+
+ + +
CHUNITHM NEW PLUS!!
+
+ + +
CHUNITHM SUN
+
+ + +
CHUNITHM SUN PLUS
+
+ + +
CHUNITHM LUMINOUS
+
+ + +
CHUNITHM LUMINOUS PLUS
+
+ + +
CHUNITHM VERSE
+
+ + +
CHUNITHM XVERSE
+
+ + +
中二节奏 NEW!!
+
+ + +
CHUNITHM Intl. SUN PLUS
+
+ + +
CHUNITHM Intl. LUMINOUS
+
+
+ + + +
Groove Coaster 4EX Infinity Highway
+
+ + + +
Hatsune Miku: Project DIVA Arcade Future Tone
+
+ + + +
Initial D Arcade Stage Zero
+
+ + + +
Kancolle Arcade
+
+ + + +
Taiko no Tatsujin
+
+ + +
WACCA REVERSE
+
+ + +
amdaemon
+
+
+
GitHub Repository
+
Web-tool by mon
+ + diff --git a/initialdzero.html b/initialdzero.html new file mode 100644 index 0000000..dc2fd88 --- /dev/null +++ b/initialdzero.html @@ -0,0 +1,152 @@ + + + + + Initial D Arcade Stage Zero DLL Modder + + + + + +

Initial D Arcade Stage Zero DLL Modder

+
WARNING: This is version 2 of the fullscreen patch, but it needs more testing.
+
+ Pro tip: Using the fullscreen patch and using Rivatuner to lock to 60fps, then setting "FullScreen = 0" and "SYNCMODE = 0" in the InitialD.ini
will let you alt-tab out + of the game, and still run at locked 60fps. +
+
Notes about the in-store patch:
+
+ The game will still require an ip in the 192.168.X.X range to boot up. However, everything past that can be freely choosen now, so using something like zerotier or a custom + vpn It's technically possible to play in-store battles. You'll need to both have matching ip-ranges in segatools.ini though, and one of you'll have to disable dipsw1, etc. +
+ + diff --git a/js/dllpatcher.js b/js/dllpatcher.js new file mode 100644 index 0000000..e25b106 --- /dev/null +++ b/js/dllpatcher.js @@ -0,0 +1,1166 @@ +/*jshint esversion: 6 */ +(function(window, document) { +"use strict"; + +// form labels often need unique IDs - this can be used to generate some +window.Patcher_uniqueid = 0; +var createID = function() { + window.Patcher_uniqueid++; + return "dllpatch_" + window.Patcher_uniqueid; +}; + +var bytesMatch = function(buffer, offset, bytes) { + for(var i = 0; i < bytes.length; i++) { + if(buffer[offset+i] != bytes[i]) + return false; + } + return true; +}; + +var bytesToHex = function(bytes) { + var s = '' + for(var i = 0; i < bytes.length; i++) { + s += bytes[i].toString(16).toUpperCase().padStart(2, '0'); + } + return s; +} + +var hexToBytes = function(hex) { + var bytes = []; + for(var i = 0; i < hex.length; i += 2) { + bytes.push(parseInt(hex.substr(i, 2), 16)); + } + return bytes; +} + +var replace = function(buffer, offset, bytes) { + for(var i = 0; i < bytes.length; i++) { + buffer[offset+i] = bytes[i]; + } +}; + +var whichBytesMatch = function(buffer, offset, bytesArray) { + for(var i = 0; i < bytesArray.length; i++) { + if(bytesMatch(buffer, offset, bytesArray[i])) + return i; + } + return -1; +}; + +// shorthand functions +var createElementClass = function(elName, className, textContent, innerHTML) { + var el = document.createElement(elName); + el.className = className || ''; + el.textContent = textContent || ''; // optional + // overrides textContent with HTML if provided + if(innerHTML) { + el.innerHTML = innerHTML; + } + return el; +}; + +var createInput = function(type, id, className) { + var el = document.createElement('input'); + el.type = type; + el.id = id; + el.className = className || ''; + return el; +}; + +var createLabel = function(labelText, htmlFor, className) { + var el = document.createElement('label'); + el.textContent = labelText; + el.htmlFor = htmlFor; + el.className = className || ''; + return el; +}; + +// Each unique kind of patch should have createUI, validatePatch, applyPatch, +// updateUI + +class StandardPatch { + constructor(options) { + this.name = options.name; + this.patches = options.patches; + this.tooltip = options.tooltip; + this.danger = options.danger; + } + + createUI(parent) { + var id = createID(); + var label = this.name; + var patch = createElementClass('div', 'patch'); + this.checkbox = createInput('checkbox', id); + patch.appendChild(this.checkbox); + patch.appendChild(createLabel(label, id)); + if(this.tooltip) { + patch.appendChild(createElementClass('div', 'tooltip', this.tooltip)); + } + if(this.danger) { + patch.appendChild(createElementClass('div', 'danger tooltip', this.danger)); + } + parent.appendChild(patch); + } + + updateUI(file) { + this.checkbox.checked = this.checkPatchBytes(file) === "on"; + } + + validatePatch(file) { + var status = this.checkPatchBytes(file); + if(status === "on") { + console.log('"' + this.name + '"', "is enabled!"); + } else if(status === "off") { + console.log('"' + this.name + '"', "is disabled!"); + } else { + return '"' + this.name + '" is neither on nor off! Have you got the right file?'; + } + } + + applyPatch(file) { + this.replaceAll(file, this.checkbox.checked); + } + + replaceAll(file, featureOn) { + for(var i = 0; i < this.patches.length; i++) { + replace(file, this.patches[i].offset, + featureOn? this.patches[i].on : this.patches[i].off); + } + } + + checkPatchBytes(file) { + var patchStatus = ""; + for(var i = 0; i < this.patches.length; i++) { + var patch = this.patches[i]; + if(bytesMatch(file, patch.offset, patch.off)) { + if(patchStatus === "") { + patchStatus = "off"; + } else if(patchStatus != "off"){ + return "on/off mismatch within patch"; + } + } else if(bytesMatch(file, patch.offset, patch.on)) { + if(patchStatus === "") { + patchStatus = "on"; + } else if(patchStatus != "on"){ + return "on/off mismatch within patch"; + } + } else { + return "patch neither on nor off"; + } + } + return patchStatus; + } +} + +class DynamicPatch { + constructor(options) { + this.name = options.name; + this.patches = options.patches; + this.tooltip = options.tooltip; + this.danger = options.danger; + this.mode = options.mode; + this.target = options.target; + } + + createUI(parent) { + var id = createID(); + var label = this.name; + this.ui = createElementClass('div', 'patch'); + this.checkbox = createInput('checkbox', id); + this.ui.appendChild(this.checkbox); + this.ui.appendChild(createLabel(label, id)); + if(this.tooltip) { + this.ui.appendChild(createElementClass('div', 'tooltip', this.tooltip)); + } + if(this.danger) { + this.ui.appendChild(createElementClass('div', 'danger tooltip', this.danger)); + } + parent.appendChild(this.ui); + } + + updateUI(file) { + if (this.mode === 'all') { + this.checkbox.checked = this.checkPatchAll(file, true) === "on"; + } else { + this.checkbox.checked = this.checkPatch(file, true) === "on"; + } + } + + validatePatch(file) { + var status = this.mode === 'all' ? this.checkPatchAll(file) : this.checkPatch(file); + + if(status === "on") { + console.log('"' + this.name + '"', "is enabled!"); + } else if(status === "off") { + console.log('"' + this.name + '"', "is disabled!"); + } else { + return '"' + this.name + '" is neither on nor off! Have you got the right file?'; + } + } + + applyPatch(file) { + this.replaceAll(file, this.checkbox.checked); + } + + replaceAll(file, featureOn) { + for(let patch of this.patches) { + if (Array.isArray(patch.offset)) { + for(const offset of patch.offset) { + if (this.target === 'string') { + replace(file, offset, + new TextEncoder().encode(featureOn? patch.on : patch.off)); + } else { + patch.on = patch.on.map((byte, idx) => byte === 'XX' ? file[offset + idx] : byte); + patch.off = patch.off.map((byte, idx) => byte === 'XX' ? file[offset + idx] : byte); + replace(file, offset, + featureOn? patch.on : patch.off); + } + } + } else { + if (this.target === 'string') { + replace(file, patch.offset, + new TextEncoder().encode(featureOn? patch.on : patch.off)); + } else { + patch.on = patch.on.map((byte, idx) => byte === 'XX' ? file[patch.offset + idx] : byte); + patch.off = patch.off.map((byte, idx) => byte === 'XX' ? file[patch.offset + idx] : byte); + replace(file, patch.offset, + featureOn? patch.on : patch.off); + } + } + } + } + + checkPatch(file, updateUiFlag = false) { + var patchStatus = ""; + let listUi; + if (updateUiFlag) { + listUi = document.createElement('ul'); + this.ui.appendChild(listUi); + } + for(var i = 0; i < this.patches.length; i++) { + var patch = this.patches[i]; + var offOffset = this.searchPatchOffset(file, patch.off, i); + var onOffset = this.searchPatchOffset(file, patch.on, i); + this.patches[i].offset = offOffset === -1 ? onOffset : offOffset; + if(offOffset > 0) { + if (updateUiFlag) { + if (this.target === 'string') { + listUi.appendChild(createElementClass('li', 'patch-off', null, '0x' + offOffset.toString(16) + ' ' + patch.off + ' will be replaced with '+ patch.on +'')); + } else { + listUi.appendChild(createElementClass('li', 'patch-off', '0x' + offOffset.toString(16) + ' will be replaced')); + } + } + if(patchStatus === "") { + patchStatus = "off"; + } + } else if(onOffset > 0) { + if (updateUiFlag) { + if (this.target === 'string') { + listUi.appendChild(createElementClass('li', 'patch-on', null, '0x' + onOffset.toString(16) + ' ' + patch.on + ' will be replaced with '+ patch.off +'')); + } else { + listUi.appendChild(createElementClass('li', 'patch-on', '0x' + onOffset.toString(16) + ' will be replaced')); + } + } + if(patchStatus === "") { + patchStatus = "on"; + } + } else if (this.mode === 'all') { + continue; + } else { + return "patch string not found"; + } + } + return patchStatus; + } + + checkPatchAll(file, updateUiFlag = false) { + var patchStatus = ""; + let listUi; + if (updateUiFlag) { + listUi = document.createElement('ul'); + this.ui.appendChild(listUi); + } + for(let patch of this.patches) { + var offOffset = this.searchPatchOffsetAll(file, patch.off); + var onOffset = this.searchPatchOffsetAll(file, patch.on); + patch.offset = offOffset.length === 0 ? onOffset : offOffset; + + if(offOffset.length > 0) { + if (updateUiFlag) { + for(const offset of offOffset) { + listUi.appendChild(createElementClass('li', 'patch-off', '0x' + offset.toString(16) + ' will be replaced')); + } + } + if(patchStatus === "") { + patchStatus = "off"; + } + } else if(onOffset.length > 0) { + if (updateUiFlag) { + for(const offset of onOffset) { + listUi.appendChild(createElementClass('li', 'patch-on', '0x' + offset.toString(16) + ' will be replaced')); + } + } + if(patchStatus === "") { + patchStatus = "on"; + } + } else { + return "patch string not found"; + } + } + return patchStatus; + } + + searchPatchOffset(file, search, offset) { + let searchBytes; + if (this.target === 'string') { + searchBytes = new TextEncoder().encode(search); + } else { + searchBytes = search; + } + + Uint8Array.prototype.indexOfArr = function(searchElements, fromIndex) { + fromIndex = fromIndex || 0; + + var index = Array.prototype.indexOf.call(this, searchElements[0], fromIndex); + if(searchElements.length === 1 || index === -1) { + return { + match: false, + index: -1, + }; + } + + for(var i = index, j = 0; j < searchElements.length && i < this.length; i++, j++) { + if (this.target !== 'string' && searchElements[j] === 'XX') { + continue; + } + if(this[i] !== searchElements[j]) { + return { + match: false, + index, + }; + } + } + return { + match: true, + index, + }; + }; + + var idx = 0; + var foundCount = 0; + for (var i = 0; i < file.length; i++) { + var result = file.indexOfArr(searchBytes, idx); + if (result.match) { + if (offset === foundCount) { + return result.index; + } + foundCount++; + } else if (result.index === -1) { + break; + } + idx = result.index + 1; + } + return -1; + } + + searchPatchOffsetAll(file, search) { + let searchBytes; + if (this.target === 'string') { + searchBytes = new TextEncoder().encode(search); + } else { + searchBytes = search; + } + + Uint8Array.prototype.indexOfArr = function(searchElements, fromIndex) { + fromIndex = fromIndex || 0; + + var index = Array.prototype.indexOf.call(this, searchElements[0], fromIndex); + if(searchElements.length === 1 || index === -1) { + return { + match: false, + index: -1, + }; + } + + for(var i = index, j = 0; j < searchElements.length && i < this.length; i++, j++) { + if (this.target !== 'string' && searchElements[j] === 'XX') { + continue; + } + if(this[i] !== searchElements[j]) { + return { + match: false, + index, + }; + } + } + + return { + match: true, + index, + }; + }; + + var idx = 0; + var foundOffsetArray = []; + for (var i = 0; i < file.length; i++) { + var result = file.indexOfArr(searchBytes, idx); + if (result.match) { + foundOffsetArray.push(result.index); + } else if (result.index === -1) { + break; + } + idx = result.index + 1; + } + return foundOffsetArray; + } +} + +// Each unique kind of patch should have createUI, validatePatch, applyPatch, +// updateUI + +// The DEFAULT state is always the 1st element in the patches array +class UnionPatch { + constructor(options) { + this.name = options.name; + this.offset = options.offset; + this.patches = options.patches; + this.tooltip = options.tooltip; + this.danger = options.danger; + } + + createUI(parent) { + this.radios = []; + var radio_id = createID(); + + var container = createElementClass('div', 'patch-union'); + container.appendChild(createElementClass('span', 'patch-union-title', this.name + ':')); + if(this.tooltip) { + container.appendChild(createElementClass('div', 'tooltip', this.tooltip)); + } + if(this.danger) { + container.appendChild(createElementClass('div', 'danger tooltip', this.danger)); + } + container.appendChild(document.createElement('span')); + + for(var i = 0; i < this.patches.length; i++) { + var patch = this.patches[i]; + var id = createID(); + var label = patch.name; + var patchDiv = createElementClass('div', 'patch'); + var radio = createInput('radio', id); + radio.name = radio_id; + this.radios.push(radio); + + patchDiv.appendChild(radio); + patchDiv.appendChild(createLabel(label, id)); + if(patch.tooltip) { + patchDiv.appendChild(createElementClass('div', 'tooltip', patch.tooltip)); + } + if(patch.danger) { + patchDiv.appendChild(createElementClass('div', 'danger tooltip', patch.danger)); + } + container.appendChild(patchDiv); + } + parent.appendChild(container); + } + + updateUI(file) { + for(var i = 0; i < this.patches.length; i++) { + if(bytesMatch(file, this.offset, this.patches[i].patch)) { + this.radios[i].checked = true; + return; + } + } + // Default fallback + this.radios[0].checked = true; + } + + validatePatch(file) { + for(var i = 0; i < this.patches.length; i++) { + if(bytesMatch(file, this.offset, this.patches[i].patch)) { + console.log(this.name, "has", this.patches[i].name, "enabled"); + return; + } + } + return '"' + this.name + '" doesn\'t have a valid patch! Have you got the right file?'; + } + + applyPatch(file) { + var patch = this.getSelected(); + replace(file, this.offset, patch.patch); + } + + getSelected() { + for(var i = 0; i < this.patches.length; i++) { + if(this.radios[i].checked) { + return this.patches[i]; + } + } + return null; + } +} + +// Each unique kind of patch should have createUI, validatePatch, applyPatch, +// updateUI +class NumberPatch { + constructor(options) { + this.name = options.name; + this.tooltip = options.tooltip; + this.danger = options.danger; + + this.offset = options.offset; + this.size = options.size; + this.min = options.min; + this.max = options.max; + this.littleEndian = options.littleEndian ?? true; + + if (![1, 2, 4].includes(this.size)) { + throw new Error(`Unsupported number size: ${this.size}.`); + } + } + + createUI(parent) { + var id = createID(); + var label = this.name; + var patch = createElementClass('div', 'patch'); + + patch.appendChild(createLabel(label, id)); + + this.number = createInput('number', id); + this.number.style.width = "3rem"; + if (this.min !== null) { + this.number.min = this.min; + } + if (this.max) { + this.number.max = this.max; + } + + patch.appendChild(this.number); + + + if (this.tooltip) { + patch.appendChild(createElementClass('div', 'tooltip', this.tooltip)); + } + if (this.danger) { + patch.appendChild(createElementClass('div', 'danger tooltip', this.danger)); + } + parent.appendChild(patch); + + } + + updateUI(file) { + const arr = new Uint8Array(this.size); + const view = new DataView(arr.buffer); + + for (var i = 0; i < this.size; i++) { + arr[i] = file[this.offset + i]; + } + + if (this.size === 1) { + this.number.value = view.getInt8(0); + } else if (this.size === 2) { + this.number.value = view.getInt16(0, this.littleEndian); + } else if (this.size === 4) { + this.number.value = view.getInt32(0, this.littleEndian); + } + } + + validatePatch(file) { + return; + } + + applyPatch(file) { + // Convert user inputted number to little endian + const arr = new Uint8Array(this.size); + const view = new DataView(arr.buffer); + + if (this.size === 1) { + view.setInt8(0, this.number.value); + } else if (this.size === 2) { + view.setInt16(0, this.number.value, this.littleEndian); + } else if (this.size === 4) { + view.setInt32(0, this.number.value, this.littleEndian); + } + + for (var i = 0; i < this.size; i++) { + file[this.offset + i] = arr[i]; + } + } +} + +// Each unique kind of patch should have createUI, validatePatch, applyPatch, +// updateUI +class HexPatch { + constructor(options) { + this.name = options.name; + this.tooltip = options.tooltip; + this.danger = options.danger; + this.offset = options.offset; + + this.off = options.off; + } + + createUI(parent) { + this.radios = []; + var radio_id = createID(); + + // Title of the radio option. + var container = createElementClass('div', 'patch-union'); + container.appendChild(createElementClass('span', 'patch-union-title', this.name + ':')); + if(this.tooltip) { + container.appendChild(createElementClass('div', 'tooltip', this.tooltip)); + } + if(this.danger) { + container.appendChild(createElementClass('div', 'danger tooltip', this.danger)); + } + container.appendChild(document.createElement('span')); + + // Default option; tooltip shows default hex value. + var id = createID(); + var patchDiv = createElementClass('div', 'patch'); + var radio = createInput('radio', id); + radio.name = radio_id; + this.radios.push(radio); + + patchDiv.appendChild(radio); + patchDiv.appendChild(createLabel('Default', id)); + patchDiv.appendChild(createElementClass('div', 'tooltip', 'Value ' + bytesToHex(this.off))); + container.appendChild(patchDiv); + + // Custom option. + id = createID(); + patchDiv = createElementClass('div', 'patch'); + radio = createInput('radio', id); + radio.name = radio_id; + this.radios.push(radio); + + patchDiv.appendChild(radio); + patchDiv.appendChild(createLabel('Custom ' + this.off.length + '-byte hex value: ', id)); + this.valueHex = document.createElement('input'); + this.valueHex.type = 'text'; + this.valueHex.id = id; + patchDiv.appendChild(this.valueHex); + + patchDiv.appendChild(createElementClass('div', 'danger tooltip', 'Invalid values will not be applied.')); + container.appendChild(patchDiv); + + parent.appendChild(container); + + } + + updateUI(file) { + if(bytesMatch(file, this.offset, this.off)) { + this.radios[0].checked = true; + return; + } + this.valueHex.value = bytesToHex(file.slice(this.offset, this.offset + this.off.length)); + this.radios[1].checked = true; + } + + validatePatch(file) { + if(bytesMatch(file, this.offset, this.off)) { + console.log(this.name, "has default hex value"); + return; + } + console.log(this.name, "has custom hex value"); + } + + applyPatch(file) { + if(this.radios[0].checked) { + replace(file, this.offset, this.off); + return; + } + if(this.radios[1].checked) { + if(!this.valueHex.value.match(/^[0-9a-fA-F]+$/)) { + alert('Patch "' + this.name + '" not applied - invalid hex!'); + return; + } + if(this.valueHex.value.length != this.off.length * 2) { + alert('Patch "' + this.name + '" not applied - invalid length!!'); + return; + } + replace(file, this.offset, hexToBytes(this.valueHex.value)); + return; + } + } +} + +var loadPatch = function(_this, self, patcher) { + patcher.loadPatchUI(); + patcher.updatePatchUI(); + patcher.container.style.display = ''; + var successStr = patcher.filename; + if (typeof _this.description === "string") { + successStr += "(" + patcher.description + ")"; + } + self.successDiv.innerHTML = successStr + " loaded successfully!"; +}; + +/** + * Check if the input is a PE executable, and if it is, parses the PE + * just enough to see if it's packed with HyperTech CrackProof, by + * checking if the first DLL import is "KeRnEl32.dLl". + * + * If there was any error parsing the executable or if it was packed, + * this function returns a string. If there were no errors, this returns + * undefined. + * + * @param {Uint8Array} dllFile + * @returns {string | undefined} + */ +var checkUnpackedExecutable = function(dllFile) { + // Note to anyone looking: all offsets are absolute file offsets. + + const DOS_EXECUTABLE_MAGIC = [0x4D, 0x5A]; + const PE_EXECUTABLE_MAGIC = [0x50, 0x45, 0x00, 0x00]; + const PE_ROM_MAGIC = 0x107; + const PE32_MAGIC = 0x10B; + const PE32_PLUS_MAGIC = 0x20B; + const OFFSET_IMAGE_OPTIONAL_HEADER32_NUMBER_OF_RVAS = 0x5C; + const OFFSET_IMAGE_OPTIONAL_HEADER64_NUMBER_OF_RVAS = 0x6C; + const SIZEOF_IMAGE_IMPORT_DESCRIPTOR = 0x28; + const CRACKPROOF_KERNEL32_DLL = [ + 0x4B, 0x65, 0x52, 0x6E, 0x45, 0x6C, 0x33, 0x32, 0x2E, 0x64, 0x4C, 0x6C, 0x00 + ]; // "KeRnEl32.dLl\0" + + // We do have patchers for other types of files, like chu.acf, + // so skip the check if we're not looking at a PE executable. + if (!bytesMatch(dllFile, 0x00, DOS_EXECUTABLE_MAGIC)) { + return undefined; + } + + const dllFileView = new DataView(dllFile.buffer); + const peHeaderOffset = dllFileView.getUint32(0x3C, true); // e_lfanew + + if (!bytesMatch(dllFile, peHeaderOffset, PE_EXECUTABLE_MAGIC)) { + // Probably a file that accidentally looks like a DOS executable... + return undefined; + } + + const numberOfSections = dllFileView.getUint16(peHeaderOffset + 0x06, true); + const sizeOfOptionalHeader = dllFileView.getUint16(peHeaderOffset + 0x14, true); + const startOfOptionalHeader = peHeaderOffset + 0x18; + const startOfSections = startOfOptionalHeader + sizeOfOptionalHeader; + + const rva2offset = (rva) => { + for (let i = 0; i < numberOfSections; i++) { + const sectionHeaderOffset = startOfSections + 0x28 * i; + const virtualSize = dllFileView.getUint32(sectionHeaderOffset + 0x08, true); + const virtualAddress = dllFileView.getUint32(sectionHeaderOffset + 0x0C, true); + const pointerToRawData = dllFileView.getUint32(sectionHeaderOffset + 0x14, true); + + if (virtualAddress <= rva && (virtualAddress + virtualSize) > rva) { + return rva - virtualAddress + pointerToRawData; + } + } + + return -1; + }; + + const magic = dllFileView.getUint16(startOfOptionalHeader, true); + + if (![PE_ROM_MAGIC, PE32_MAGIC, PE32_PLUS_MAGIC].includes(magic)) { + return `Unknown PE format ${magic}.`; + } + + const numberOfRvaAndSizesOffset = startOfOptionalHeader + ( + magic === PE32_PLUS_MAGIC + ? OFFSET_IMAGE_OPTIONAL_HEADER64_NUMBER_OF_RVAS + : OFFSET_IMAGE_OPTIONAL_HEADER32_NUMBER_OF_RVAS + ); + const numberOfRvaAndSizes = dllFileView.getUint32(numberOfRvaAndSizesOffset, true); + + // No import directory, apparently. Not our problem then. + if (numberOfRvaAndSizes < 2) { + return undefined; + } + + const startOfDataDirectories = numberOfRvaAndSizesOffset + 0x04; + + // The import directory is the second directory (index 1), as can be seen in winnt.h: + // #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 + const importDirectoryRva = dllFileView.getUint32(startOfDataDirectories + 0x08, true); + const importDirectorySize = dllFileView.getUint32(startOfDataDirectories + 0x0C, true); + + // No imports, also not our problem. + if (importDirectorySize < SIZEOF_IMAGE_IMPORT_DESCRIPTOR) { + return undefined; + } + + const importDirectoryOffset = rva2offset(importDirectoryRva); + + if (importDirectoryOffset === -1) { + return "Could not convert import directory RVA to offset."; + } + + const importDllNameRva = dllFileView.getUint32(importDirectoryOffset + 12, true); + const importDllNameOffset = rva2offset(importDllNameRva); + + if (importDllNameOffset === -1) { + return "Could not convert import DLL name RVA to offset."; + } + + if (bytesMatch(dllFile, importDllNameOffset, CRACKPROOF_KERNEL32_DLL)) { + return "This executable is packed with CrackProof. Please obtain an unpacked executable."; + } + + return undefined; +} + +class PatchContainer { + constructor(patchers) { + this.patchers = patchers; + this.createUI(); + } + + getSupportedDLLs() { + var dlls = []; + for (var i = 0; i < this.patchers.length; i++) { + var name = this.patchers[i].filename; + if (dlls.indexOf(name) === -1) { + dlls.push(name); + } + } + return dlls; + } + + createUI() { + var self = this; + var container = createElementClass('div', 'patchContainer'); + var header = this.getSupportedDLLs().join(", "); + container.innerHTML = "

" + header + "

"; + + var supportedDlls = document.createElement('ul'); + this.forceLoadTexts = []; + this.forceLoadButtons = []; + this.matchSuccessText = []; + for (var i = 0; i < this.patchers.length; i++) { + var checkboxId = createID(); + + var listItem = document.createElement('li'); + listItem.appendChild(createLabel(this.patchers[i].description, checkboxId, 'patchPreviewLabel')); + var matchPercent = createElementClass('span', 'matchPercent'); + this.forceLoadTexts.push(matchPercent); + listItem.appendChild(matchPercent); + var matchSuccess = createElementClass('span', 'matchSuccess'); + this.matchSuccessText.push(matchSuccess); + listItem.appendChild(matchSuccess); + var forceButton = createElementClass('button', '', 'Force load?'); + forceButton.style.display = 'none'; + this.forceLoadButtons.push(forceButton); + listItem.appendChild(forceButton); + + var input = createInput('checkbox', checkboxId, 'patchPreviewToggle'); + listItem.appendChild(input); + var patchPreviews = createElementClass('ul', 'patchPreview'); + for (var j = 0; j < this.patchers[i].mods.length; j++) { + var patchName = this.patchers[i].mods[j].name; + patchPreviews.appendChild(createElementClass('li', null, patchName)); + } + listItem.appendChild(patchPreviews); + + supportedDlls.appendChild(listItem); + } + + ["dragover", "dragenter"].forEach(function(n){ + document.documentElement.addEventListener(n,function (e) { + container.classList.add("dragover"); + e.preventDefault(); + e.stopPropagation(); + }); + }); + ["dragleave", "dragend", "drop"].forEach(function(n){ + document.documentElement.addEventListener(n,function (e) { + container.classList.remove("dragover"); + e.preventDefault(); + e.stopPropagation(); + }); + }); + + container.addEventListener("drop", function (e) { + var files = e.dataTransfer.files; + if (files && files.length > 0) + self.loadFile(files[0]); + }); + + var filepickerId = createID(); + this.fileInput = createInput('file', filepickerId, 'fileInput'); + var label = createLabel('', filepickerId, 'fileLabel'); + label.innerHTML = "Choose a file or drag and drop."; + + this.fileInput.addEventListener("change", function (e) { + if (this.files && this.files.length > 0) + self.loadFile(this.files[0]); + }); + + this.successDiv = createElementClass('div', 'success'); + this.errorDiv = createElementClass('div', 'error'); + + container.appendChild(this.fileInput); + container.appendChild(label); + + container.appendChild(createElementClass('h4', null, 'Supported Versions:')); + container.appendChild(createElementClass('h5', null, 'Click name to preview patches')); + container.appendChild(supportedDlls); + container.appendChild(this.successDiv); + container.appendChild(this.errorDiv); + document.body.appendChild(container); + } + + loadFile(file) { + var reader = new FileReader(); + var self = this; + + reader.onload = function (e) { + var found = false; + // clear logs + self.errorDiv.textContent = ''; + self.successDiv.textContent = ''; + + const error = checkUnpackedExecutable(new Uint8Array(e.target.result)); + + if (error) { + self.errorDiv.innerHTML = error; + return; + } + + for (var i = 0; i < self.patchers.length; i++) { + // reset text and buttons + self.forceLoadButtons[i].style.display = 'none'; + self.forceLoadTexts[i].textContent = ''; + self.matchSuccessText[i].textContent = ''; + var patcher = self.patchers[i]; + // remove the previous UI to clear the page + patcher.destroyUI(); + // patcher UI elements have to exist to load the file + patcher.createUI(); + patcher.container.style.display = 'none'; + patcher.loadBuffer(e.target.result); + if (patcher.validatePatches()) { + found = true; + loadPatch(this, self, patcher); + // show patches matched for 100% - helps identify which version is loaded + var valid = patcher.validPatches; + self.matchSuccessText[i].textContent = ' ' + valid + ' of ' + valid + ' patches matched (100%) '; + } + } + + if (!found) { + // let the user force a match + for (let i = 0; i < self.patchers.length; i++) { + const patcher = self.patchers[i]; + + const valid = patcher.validPatches; + const percent = (valid / patcher.totalPatches * 100).toFixed(1); + + self.forceLoadTexts[i].textContent = ' ' + valid + ' of ' + patcher.totalPatches + ' patches matched (' + percent + '%) '; + self.forceLoadButtons[i].style.display = ''; + self.forceLoadButtons[i].onclick = function(i) { + // reset old text + for(var j = 0; j < self.patchers.length; j++) { + self.forceLoadButtons[j].style.display = 'none'; + self.forceLoadTexts[j].textContent = ''; + } + + + loadPatch(this, self, self.patchers[i]); + }.bind(this, i); + } + self.errorDiv.innerHTML = "No patch set was a 100% match."; + } + }; + + reader.readAsArrayBuffer(file); + } +} + +class Patcher { + constructor(fname, description, args) { + this.mods = []; + for(var i = 0; i < args.length; i++) { + var mod = args[i]; + if(mod.type) { + if(mod.type === "union") { + this.mods.push(new UnionPatch(mod)); + } + if(mod.type === "number") { + this.mods.push(new NumberPatch(mod)); + } + if(mod.type === "dynamic") { + this.mods.push(new DynamicPatch(mod)); + } + if(mod.type === "hex") { + this.mods.push(new HexPatch(mod)); + } + } else { // standard patch + this.mods.push(new StandardPatch(mod)); + } + } + + this.filename = fname; + this.description = description; + this.multiPatcher = true; + + if (!this.description) { + // old style patcher, use the old method to generate the UI + this.multiPatcher = false; + this.createUI(); + this.loadPatchUI(); + } + } + + createUI() { + var self = this; + this.container = createElementClass('div', 'patchContainer'); + var header = this.filename; + if(this.description === "string") { + header += ' (' + this.description + ')'; + } + this.container.innerHTML = '

' + header + '

'; + + this.successDiv = createElementClass('div', 'success'); + this.errorDiv = createElementClass('div', 'error'); + this.patchDiv = createElementClass('div', 'patches'); + + var saveButton = document.createElement('button'); + saveButton.disabled = true; + saveButton.textContent = 'Load file First'; + saveButton.addEventListener('click', this.saveDll.bind(this)); + this.saveButton = saveButton; + + if (!this.multiPatcher) { + ["dragover", "dragenter"].forEach(function(n){ + document.documentElement.addEventListener(n,function(e) { + self.container.classList.add('dragover'); + e.preventDefault(); + return true; + }); + }); + ["dragleave", "dragend", "drop"].forEach(function(n){ + document.documentElement.addEventListener(n,function(e) { + self.container.classList.remove('dragover'); + e.preventDefault(); + return true; + }); + }); + + this.container.addEventListener('drop', function(e) { + var files = e.dataTransfer.files; + if(files && files.length > 0) + self.loadFile(files[0]); + }); + + var filepickerId = createID(); + this.fileInput = createInput('file', filepickerId, 'fileInput'); + var label = createLabel('', filepickerId, 'fileLabel'); + label.innerHTML = 'Choose a file or drag and drop.'; + + this.fileInput.addEventListener('change', function(e) { + if(this.files && this.files.length > 0) + self.loadFile(this.files[0]); + }); + + this.container.appendChild(this.fileInput); + this.container.appendChild(label); + } + + this.container.appendChild(this.successDiv); + this.container.appendChild(this.errorDiv); + this.container.appendChild(this.patchDiv); + this.container.appendChild(saveButton); + document.body.appendChild(this.container); + } + + destroyUI() { + if (this.hasOwnProperty("container")) + this.container.remove(); + } + + loadBuffer(buffer) { + this.dllFile = new Uint8Array(buffer); + if(this.validatePatches()) { + this.successDiv.classList.remove("hidden"); + this.successDiv.innerHTML = "File loaded successfully!"; + } else { + this.successDiv.classList.add("hidden"); + } + // Update save button regardless + this.saveButton.disabled = false; + this.saveButton.textContent = 'Save Patched File'; + this.errorDiv.innerHTML = this.errorLog; + } + + loadFile(file) { + var reader = new FileReader(); + var self = this; + + reader.onload = function(e) { + self.loadBuffer(e.target.result); + self.updatePatchUI(); + }; + + reader.readAsArrayBuffer(file); + } + + downloadURI(uri, filename) { + // http://stackoverflow.com/a/18197341 + var element = document.createElement('a'); + element.setAttribute('href', uri); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + } + + saveDll() { + if(!this.dllFile || !this.mods || !this.filename) + return; + + for(var i = 0; i < this.mods.length; i++) { + this.mods[i].applyPatch(this.dllFile); + } + + var blob = new Blob([this.dllFile], {type: "application/octet-stream"}); + var uri = URL.createObjectURL(blob); + this.downloadURI(uri, this.filename); + URL.revokeObjectURL(uri); + } + + loadPatchUI() { + for(var i = 0; i < this.mods.length; i++) { + this.mods[i].createUI(this.patchDiv); + } + } + + updatePatchUI() { + for(var i = 0; i < this.mods.length; i++) { + this.mods[i].updateUI(this.dllFile); + } + } + + validatePatches() { + this.errorLog = ""; + var success = true; + this.validPatches = 0; + this.totalPatches = this.mods.length; + for(var i = 0; i < this.mods.length; i++) { + var error = this.mods[i].validatePatch(this.dllFile); + if(error) { + this.errorLog += error + "
"; + success = false; + } else { + this.validPatches++; + } + } + return success; + } +} + +window.Patcher = Patcher; +window.PatchContainer = PatchContainer; + +})(window, document); diff --git a/kancolle.html b/kancolle.html new file mode 100644 index 0000000..0e994dd --- /dev/null +++ b/kancolle.html @@ -0,0 +1,74 @@ + + + + + Kancolle Arcade Modder + + + + + +

Kancolle Arcade Modder

+ + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..3ae4ade --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "bemanipatcher", + "version": "1.0.0", + "description": "Online DLL patcher. This file only exists for eslint.", + "author": "mon", + "devDependencies": { + "eslint": "^8.52.0", + "eslint-plugin-html": "^7.1.0" + } +} diff --git a/taikonotatsujin.html b/taikonotatsujin.html new file mode 100644 index 0000000..bd32b6f --- /dev/null +++ b/taikonotatsujin.html @@ -0,0 +1,154 @@ + + + + + Taiko no Tatsujin (Nijiiro Ver.) Modder + + + + + +

Taiko no Tatsujin (Nijiiro Ver.) Modder

+ + diff --git a/waccareverse.html b/waccareverse.html new file mode 100644 index 0000000..7948769 --- /dev/null +++ b/waccareverse.html @@ -0,0 +1,68 @@ + + + + + + WACCA REVERSE Modder + + + + + + +

WACCA REVERSE Modder

+ + + \ No newline at end of file