├── .envrc ├── pm64-typegen ├── index.js ├── .gitignore ├── Cargo.toml ├── package.json └── src │ └── main.rs ├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── mamar-web ├── .gitignore ├── src │ ├── app │ │ ├── doc │ │ │ ├── SubsegDetails.module.scss │ │ │ ├── ActiveDoc.module.scss │ │ │ ├── timectx.ts │ │ │ ├── Playhead.module.scss │ │ │ ├── Tracker.module.scss │ │ │ ├── Playhead.tsx │ │ │ ├── SegmentMap.module.scss │ │ │ ├── Ruler.module.scss │ │ │ ├── ActiveDoc.tsx │ │ │ ├── SegmentMap.tsx │ │ │ └── SubsegDetails.tsx │ │ ├── sbn │ │ │ ├── BgmFromSbnPicker.scss │ │ │ ├── worker.ts │ │ │ ├── useDecodedSbn.ts │ │ │ ├── BgmFromSbnPicker.tsx │ │ │ └── songNames.json │ │ ├── Main.tsx │ │ ├── icon │ │ │ ├── LoadingSpinner.tsx │ │ │ └── loadingspinner.svg │ │ ├── InstrumentInput.module.scss │ │ ├── store │ │ │ ├── index.ts │ │ │ ├── segment.ts │ │ │ ├── doc.ts │ │ │ ├── dispatch.ts │ │ │ ├── root.ts │ │ │ ├── bgm.ts │ │ │ └── variation.ts │ │ ├── App.module.scss │ │ ├── NoteInput.module.scss │ │ ├── StringInput.module.scss │ │ ├── special-imports.d.ts │ │ ├── WelcomeScreen.tsx │ │ ├── util │ │ │ ├── Patcher.ts │ │ │ ├── hooks │ │ │ │ ├── useSize.ts │ │ │ │ ├── useSelection.tsx │ │ │ │ ├── useRomData.tsx │ │ │ │ ├── useUndoReducer.ts │ │ │ │ └── useMupen.tsx │ │ │ └── DramView.ts │ │ ├── header │ │ │ ├── SponsorButton.tsx │ │ │ ├── SponsorButton.module.scss │ │ │ ├── Header.scss │ │ │ ├── Header.tsx │ │ │ ├── OpenButton.tsx │ │ │ └── BgmActionGroup.tsx │ │ ├── VerticalDragNumberInput.module.scss │ │ ├── StringInput.tsx │ │ ├── emu │ │ │ ├── TrackControls.module.scss │ │ │ ├── TrackControls.tsx │ │ │ ├── PlaybackControls.module.scss │ │ │ └── PaperMarioRomInputDialog.tsx │ │ ├── ErrorBoundaryView.tsx │ │ ├── index.tsx │ │ ├── analytics.ts │ │ ├── DocTabs.tsx │ │ ├── NoteInput.tsx │ │ ├── index.html │ │ ├── VerticalDragNumberInput.tsx │ │ ├── DocTabs.scss │ │ ├── App.tsx │ │ └── InstrumentInput.tsx │ ├── macos_icon.png │ ├── screenshot.png │ ├── social-banner.png │ ├── service-worker-load.js │ ├── manifest.json │ ├── service-worker.js │ ├── index.html │ ├── logo.svg │ └── index.scss ├── vercel.json ├── .editorconfig ├── .parcelrc ├── tsconfig.json └── package.json ├── .gitattributes ├── pm64 ├── src │ ├── lib.rs │ ├── id.rs │ ├── sbn │ │ ├── en.rs │ │ ├── mod.rs │ │ └── de.rs │ ├── main.rs │ ├── bgm │ │ └── mamar.rs │ └── rw.rs ├── tests │ ├── bin │ │ ├── .gitignore │ │ └── extract.py │ └── readme.md └── Cargo.toml ├── patches ├── build │ └── .gitignore ├── yarn.lock ├── src │ ├── patches.ld │ └── patches.c ├── package.json ├── test.js └── build.py ├── .gitignore ├── .gitmodules ├── .vercelignore ├── rustfmt.toml ├── n64crc ├── Makefile ├── test.html └── n64crc.c ├── Cargo.toml ├── .editorconfig ├── package.json ├── license ├── mamar-wasm-bridge ├── Cargo.toml └── src │ └── lib.rs ├── .vscode ├── settings.json ├── c_cpp_properties.json └── tasks.json ├── flake.nix ├── flake.lock ├── .eslintrc.json └── changelog.md /.envrc: -------------------------------------------------------------------------------- 1 | use flake -------------------------------------------------------------------------------- /pm64-typegen/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pm64-typegen/.gitignore: -------------------------------------------------------------------------------- 1 | pm64.d.ts 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: bates64 2 | -------------------------------------------------------------------------------- /mamar-web/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | *.log 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | patches/build/**/* linguist-generated=true 2 | -------------------------------------------------------------------------------- /pm64/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bgm; 2 | pub mod id; 3 | mod rw; 4 | pub mod sbn; 5 | -------------------------------------------------------------------------------- /patches/build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !*.js 4 | !*.mjs 5 | !*.d.ts 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.log 3 | node_modules 4 | .parcel-cache 5 | .vercel 6 | .direnv 7 | -------------------------------------------------------------------------------- /mamar-web/src/app/doc/SubsegDetails.module.scss: -------------------------------------------------------------------------------- 1 | .regionName { 2 | margin: 0; 3 | } 4 | -------------------------------------------------------------------------------- /mamar-web/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "silent": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /mamar-web/src/macos_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bates64/mamar/HEAD/mamar-web/src/macos_icon.png -------------------------------------------------------------------------------- /mamar-web/src/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bates64/mamar/HEAD/mamar-web/src/screenshot.png -------------------------------------------------------------------------------- /mamar-web/src/social-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bates64/mamar/HEAD/mamar-web/src/social-banner.png -------------------------------------------------------------------------------- /patches/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "patches/papermario"] 2 | path = patches/papermario 3 | url = git@github.com:pmret/papermario.git 4 | -------------------------------------------------------------------------------- /patches/src/patches.ld: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .text 0x80400000 : { 4 | ../build/patches.o(.text) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /pm64/tests/bin/.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.z64 3 | *.ron 4 | 5 | # debug output from failed tests 6 | *.decoded.txt 7 | *.nonmatching.bin 8 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | target 2 | patches/papermario 3 | patches/patches.* 4 | !patches/patches.js 5 | !patches/patches.mjs 6 | !patches/patches.d.ts 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | comment_width = 120 3 | wrap_comments = true 4 | newline_style = "unix" 5 | use_field_init_shorthand = true 6 | -------------------------------------------------------------------------------- /mamar-web/src/app/doc/ActiveDoc.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | height: calc(100vh - 36px - 38px); 3 | overflow: hidden; 4 | display: grid; 5 | } 6 | -------------------------------------------------------------------------------- /mamar-web/src/app/sbn/BgmFromSbnPicker.scss: -------------------------------------------------------------------------------- 1 | .BgmFromSbnPicker { 2 | div[role="row"]:not([aria-rowindex="1"]) { 3 | cursor: pointer; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /mamar-web/src/app/Main.tsx: -------------------------------------------------------------------------------- 1 | import DocTabs from "./DocTabs" 2 | 3 | export default function Main() { 4 | return
5 | 6 |
7 | } 8 | -------------------------------------------------------------------------------- /pm64/tests/readme.md: -------------------------------------------------------------------------------- 1 | Many of these tests run on data extracted from a vanilla Paper Mario (U) ROM. 2 | Run the following script to extract them: 3 | 4 | $ ./bin/extract.py path/to/papermario.z64 5 | -------------------------------------------------------------------------------- /mamar-web/src/app/icon/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | const src = new URL("./loadingspinner.svg", import.meta.url).href 2 | 3 | export default function LoadingSpinner() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /mamar-web/src/service-worker-load.js: -------------------------------------------------------------------------------- 1 | navigator.serviceWorker 2 | .register(new URL("./service-worker.js", import.meta.url), { type: "module" }) 3 | .then(registration => { 4 | return registration.update() 5 | }) 6 | -------------------------------------------------------------------------------- /mamar-web/src/app/InstrumentInput.module.scss: -------------------------------------------------------------------------------- 1 | .actionButton { 2 | all: unset; 3 | background: unset !important; 4 | cursor: pointer; 5 | 6 | &:hover { 7 | background: #00000020 !important; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /pm64-typegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pm64-typegen" 3 | version = "0.1.0" 4 | authors = ["Alex Bates "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | typescript-type-def = "0.5" 9 | pm64 = { path = "../pm64" } 10 | -------------------------------------------------------------------------------- /pm64/src/id.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU32, Ordering}; 2 | 3 | pub type Id = u32; 4 | 5 | pub fn gen_id() -> Id { 6 | static NEXT_ID: AtomicU32 = AtomicU32::new(0); 7 | 8 | NEXT_ID.fetch_add(1, Ordering::Relaxed) 9 | } 10 | -------------------------------------------------------------------------------- /mamar-web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [package.json] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /pm64-typegen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pm64-typegen", 3 | "version": "0.1.0", 4 | "module": "index.js", 5 | "private": true, 6 | "main": "index.js", 7 | "types": "pm64.d.ts", 8 | "scripts": { 9 | "build": "cargo run" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mamar-web/src/app/store/index.ts: -------------------------------------------------------------------------------- 1 | export { useRoot } from "./dispatch" 2 | export { useDoc } from "./doc" 3 | export type { Doc } from "./doc" 4 | export { useBgm } from "./bgm" 5 | export { useVariation } from "./variation" 6 | export { useSegment } from "./segment" 7 | -------------------------------------------------------------------------------- /mamar-web/src/app/App.module.scss: -------------------------------------------------------------------------------- 1 | .playbackControlsContainer { 2 | position: absolute; 3 | top: 12px; 4 | left: 50%; 5 | transform: translateX(-50%); 6 | 7 | :global(body.window-controls-overlay) & { 8 | top: env(titlebar-area-height, 12px); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /n64crc/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS ?= -O2 2 | 3 | all: build/n64crc.mjs build/n64crc 4 | 5 | build/n64crc: n64crc.c 6 | gcc $(CFLAGS) -o $@ $< 7 | 8 | build/n64crc.mjs: n64crc.c 9 | emcc $(CFLAGS) -DEMSCRIPTEN -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -o $@ $< 10 | 11 | clean: 12 | rm -f build/n64crc.mjs build/n64crc 13 | -------------------------------------------------------------------------------- /mamar-web/.parcelrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@parcel/config-default", 3 | "reporters": [ 4 | "...", 5 | "parcel-reporter-multiple-static-file-copier" 6 | ], 7 | "transformers": { 8 | "jsx:*.svg": ["...", "@parcel/transformer-svg-react"], 9 | "jsx:*": ["..."] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mamar-web/src/app/NoteInput.module.scss: -------------------------------------------------------------------------------- 1 | .input { 2 | color: inherit; 3 | background: transparent; 4 | border: 0; 5 | font: inherit; 6 | text-align: center; 7 | 8 | cursor: text; 9 | 10 | &:active, 11 | &:focus { 12 | color: #fff; 13 | outline: none; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "mamar-wasm-bridge", 4 | "pm64", 5 | "pm64-typegen", 6 | ] 7 | 8 | [profile.release] 9 | lto = true 10 | #codegen-units = 1 # slow compile, but makes more optimisations possible 11 | opt-level = 3 # could also use "s" to optimise for code size 12 | panic = "abort" 13 | -------------------------------------------------------------------------------- /mamar-web/src/app/StringInput.module.scss: -------------------------------------------------------------------------------- 1 | .input { 2 | color: inherit; 3 | background: transparent; 4 | border: 0; 5 | font: inherit; 6 | text-align: center; 7 | 8 | cursor: text; 9 | 10 | &:active, 11 | &:focus { 12 | color: var(--yellow); 13 | outline: none; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /patches/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patches", 3 | "version": "0.1.0", 4 | "private": true, 5 | "main": "build/patches.js", 6 | "module": "build/patches.mjs", 7 | "types": "build/patches.d.ts", 8 | "scripts": { 9 | "build": "python3 build.py", 10 | "build-papermario": "cd papermario && ./install.sh && ./configure us && ninja" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /mamar-web/src/app/special-imports.d.ts: -------------------------------------------------------------------------------- 1 | declare module "jsx:*.svg" { 2 | import { ComponentType, SVGProps } from "react" 3 | 4 | const SVGComponent: ComponentType> 5 | export default SVGComponent 6 | } 7 | 8 | declare module "*.module.scss" { 9 | const styles: { [key: string]: string } 10 | export default styles 11 | } 12 | -------------------------------------------------------------------------------- /mamar-web/src/app/sbn/worker.ts: -------------------------------------------------------------------------------- 1 | import * as WasmBridge from "mamar-wasm-bridge" 2 | 3 | WasmBridge.default().then(() => { 4 | WasmBridge.init_logging() 5 | postMessage("READY") 6 | }) 7 | 8 | onmessage = evt => { 9 | const romData = evt.data as ArrayBuffer 10 | const sbn = WasmBridge.sbn_decode(new Uint8Array(romData)) 11 | postMessage(sbn) 12 | } 13 | -------------------------------------------------------------------------------- /mamar-web/src/app/WelcomeScreen.tsx: -------------------------------------------------------------------------------- 1 | import { View } from "@adobe/react-spectrum" 2 | 3 | import BgmFromSbnPicker from "./sbn/BgmFromSbnPicker" 4 | 5 | export default function WelcomeScreen() { 6 | return 10 |

✨ Welcome to Mamar! ✨

11 | 12 |
13 | } 14 | -------------------------------------------------------------------------------- /mamar-web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "es2021", 5 | "strict": true, 6 | "isolatedModules": true, 7 | "jsx": "preserve", 8 | "moduleResolution": "node", 9 | "module": "ES2020", 10 | "resolveJsonModule": true, 11 | "allowSyntheticDefaultImports": true 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{yml,yaml,ron,nix}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [{package-lock,package}.json] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [Makefile] 20 | indent_style = tab 21 | -------------------------------------------------------------------------------- /mamar-web/src/app/util/Patcher.ts: -------------------------------------------------------------------------------- 1 | export default class Patcher { 2 | dv: DataView 3 | 4 | constructor(rom: ArrayBuffer) { 5 | this.dv = new DataView(rom) 6 | } 7 | 8 | overwriteFunction(romAddr: number, dataU32: number[]) { 9 | for (let i = 0; i < dataU32.length; i++) { 10 | this.dv.setUint32(romAddr + i * 4, dataU32[i], false) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pm64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pm64" 3 | version = "0.1.0" 4 | authors = ["Alex Bates "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | lazy_static = "1" 9 | log = "0.4" 10 | midly = { version = "0.5", optional = true, default-features = false, features = ["std", "alloc"] } 11 | rmp-serde = "1.3.0" 12 | serde = "1" 13 | serde_derive = "1" 14 | typescript-type-def = "0.5" 15 | 16 | [build-dependencies] 17 | typescript-type-def = "0.5" 18 | -------------------------------------------------------------------------------- /mamar-web/src/app/header/SponsorButton.tsx: -------------------------------------------------------------------------------- 1 | import { Heart } from "react-feather" 2 | 3 | import styles from "./SponsorButton.module.scss" 4 | 5 | export default function SponsorButton() { 6 | return 12 | 13 | Support development 14 | 15 | } 16 | -------------------------------------------------------------------------------- /pm64/src/sbn/en.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::prelude::*; 3 | 4 | use super::*; 5 | 6 | type Error = io::Error; 7 | 8 | type Result = std::result::Result; 9 | 10 | impl Sbn { 11 | pub fn as_bytes(&self) -> Result> { 12 | let mut encoded = io::Cursor::new(Vec::new()); 13 | self.encode(&mut encoded)?; 14 | Ok(encoded.into_inner()) 15 | } 16 | 17 | pub fn encode(&self, _: &mut W) -> Result<()> { 18 | todo!("SBN encoding") // TODO 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mamar-web/src/app/doc/timectx.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react" 2 | 3 | export interface Time { 4 | xToTicks(clientX: number): number 5 | ticksToXOffset(ticks: number): number 6 | } 7 | 8 | const TIME_CTX = createContext