Add Gambatte GB/GBC emulator, Call/SMS anti-spam blocklist, and Lua/bc/dc/Yabasic

This commit is contained in:
Luxferre
2026-10-26 15:17:55 +02:00
parent b7f0dc08c9
commit 6db14be799
113 changed files with 20006 additions and 237 deletions
+1
View File
@@ -21,6 +21,7 @@
*.tar.gz *.tar.gz
*.tar.xz *.tar.xz
*.tar.zst *.tar.zst
*.zip
# Private SSH keys, credentials, and local agent configs # Private SSH keys, credentials, and local agent configs
/keys/phone_id_* /keys/phone_id_*
+2 -2
View File
@@ -16,7 +16,7 @@ It uses a pure **X11 (`Xlibre` + `sxmo-de-dwm`)** stack (no Wayland) optimized f
- `src/xdrawer/`: Global phone controls overlay (`xdrawer.so` / `xdrawer`), toggled by pressing both softkeys simultaneously. - `src/xdrawer/`: Global phone controls overlay (`xdrawer.so` / `xdrawer`), toggled by pressing both softkeys simultaneously.
- `src/sxmo-st/`: Patched `st` terminal with HarfBuzz OpenType ligatures (`Fira Code`), `XIM` (`fcitx5-t9`), and live `SIGUSR1` `.Xresources` reload. - `src/sxmo-st/`: Patched `st` terminal with HarfBuzz OpenType ligatures (`Fira Code`), `XIM` (`fcitx5-t9`), and live `SIGUSR1` `.Xresources` reload.
- `src/fcitx5-t9/`: 6-mode keypad input method (`abc`, `Abc`, `ABC`, `Sym`, `Ctl`, `123` + `*` long-press `Ctrl+` latch) with fixed bottom preedit bar (`Y=290, H=30`). - `src/fcitx5-t9/`: 6-mode keypad input method (`abc`, `Abc`, `ABC`, `Sym`, `Ctl`, `123` + `*` long-press `Ctrl+` latch) with fixed bottom preedit bar (`Y=290, H=30`).
- `src/games/`: Standalone keypad-driven `240x320` X11 games (`snake.cpp` -> `/usr/bin/snake`, `tetris.cpp` -> `/usr/bin/tetris`) with built-in 5x7 bitmap fonts, `timerfd`-driven pacing, and offscreen `Pixmap` double buffering. - `src/games/`: Standalone keypad-driven `240x320` X11 games and emulators (`snake.cpp` -> `/usr/bin/snake`, `tetris.cpp` -> `/usr/bin/tetris`, `gambatte_phone.cpp` + `gambatte/` -> `/usr/bin/gambatte` / `/usr/bin/gbemu`) with built-in 5x7 bitmap fonts, `timerfd`-driven pacing, and offscreen `Pixmap` double buffering.
- `src/msm8905-cam/` & `src/leds-msm8905-torch/`: Out-of-tree Linux `6.12.1-msm8916` kernel modules for the rear `GC2395` camera (`/dev/video0`) and `SY7807` torch LED (`white:torch`) / WCD codec init / PM8909 `PON` shutdown. - `src/msm8905-cam/` & `src/leds-msm8905-torch/`: Out-of-tree Linux `6.12.1-msm8916` kernel modules for the rear `GC2395` camera (`/dev/video0`) and `SY7807` torch LED (`white:torch`) / WCD codec init / PM8909 `PON` shutdown.
- `overlay/rootfs/`: System configuration overlay applied over the target rootfs (kept in sync with `prebuilt/`). - `overlay/rootfs/`: System configuration overlay applied over the target rootfs (kept in sync with `prebuilt/`).
- `prebuilt/`: Cross-compiled `armv7` binaries and kernel modules (`sxmo-x11`, `fcitx5-t9`, `xlibre`, `kernel-6.12.1-msm8916`, `bantam`, `games`). - `prebuilt/`: Cross-compiled `armv7` binaries and kernel modules (`sxmo-x11`, `fcitx5-t9`, `xlibre`, `kernel-6.12.1-msm8916`, `bantam`, `games`).
@@ -26,7 +26,7 @@ It uses a pure **X11 (`Xlibre` + `sxmo-de-dwm`)** stack (no Wayland) optimized f
All cross-compilation scripts run inside rootless Docker (`alpine:3.24` `armv7` sysroot) and automatically update both `prebuilt/` and `overlay/rootfs/`: All cross-compilation scripts run inside rootless Docker (`alpine:3.24` `armv7` sysroot) and automatically update both `prebuilt/` and `overlay/rootfs/`:
- `./scripts/build-sxmo-x11.sh` — Builds `st`, `dmenu.so`, `dwm`, `dwm-plugin`, `sxmo_vibrate`, and all C++ `.so` plugins (`xdrawer`, `dialer`, `calllog`, `callscreen`, `contacts`, `sms`, `files`, `camera`, `themes`, `modem`, `networks`, `ringtones`, `wallpapers`, `music`). - `./scripts/build-sxmo-x11.sh` — Builds `st`, `dmenu.so`, `dwm`, `dwm-plugin`, `sxmo_vibrate`, and all C++ `.so` plugins (`xdrawer`, `dialer`, `calllog`, `callscreen`, `contacts`, `sms`, `files`, `camera`, `themes`, `modem`, `networks`, `ringtones`, `wallpapers`, `music`).
- `./scripts/build-dmenu.sh` — Rebuilds only `dmenu.so`, `stest`, `dmenu_path`, and `dmenu_run`. - `./scripts/build-dmenu.sh` — Rebuilds only `dmenu.so`, `stest`, `dmenu_path`, and `dmenu_run`.
- `./scripts/build-games.sh` — Builds standalone X11 games in `src/games/` (`snake`, `tetris`). - `./scripts/build-games.sh` — Builds standalone X11 games and emulators in `src/games/` (`snake`, `tetris`, `gambatte` / `gbemu`).
- `./scripts/build-fcitx5-t9.sh` — Builds `src/fcitx5-t9` (`libt9.so` / `t9.so`). - `./scripts/build-fcitx5-t9.sh` — Builds `src/fcitx5-t9` (`libt9.so` / `t9.so`).
- `./scripts/build-kernel-audio.sh` — Builds `6.12.1-msm8916` QDSP6 audio, torch LED, camera `.ko` modules, and device `.dtb` files. - `./scripts/build-kernel-audio.sh` — Builds `6.12.1-msm8916` QDSP6 audio, torch LED, camera `.ko` modules, and device `.dtb` files.
- `./scripts/build-xlibre.sh` — Builds Xlibre `Xorg` and `libinput`/`fbdev` DDX drivers. - `./scripts/build-xlibre.sh` — Builds Xlibre `Xorg` and `libinput`/`fbdev` DDX drivers.
+19
View File
@@ -6,6 +6,25 @@ Release versions follow the `<year-letter><MM><DD>` format (where `2026` is `a`,
--- ---
## Build `a1026` — codename `argonaut` (2026-10-26)
- **Download the recent images for your device from this link**: https://cloud.disroot.org/s/dB7bM7sRTScetDJ
### New features and improvements
- **Anti-spam blocklist in Call Log (`xcalllog`) and SMS (`xsms`)**:
- Unified number blocklist (`~/.config/sxmo/block.tsv`) supporting exact numbers, 9-digit suffix matching across local/international formats, and prefix wildcards (`*`).
- Automatic rejection of incoming calls from blocked numbers via `sxmo_hook_block_call.sh`, logging blocked attempts into the `Spam` tab (`~/.local/share/sxmo/block/modemlog.tsv`) without ringing or missed-call alerts.
- Silent routing of incoming SMS messages from blocked senders into the `Spam` folder (`~/.local/share/sxmo/block`), with one-key thread blocking, unblocking, and restoration between `Inbox` and `Spam` in `xsms`.
- **Native Game Boy / Game Boy Color emulator (`gambatte` / `gbemu`)**:
- Full-featured C++ Game Boy / Game Boy Color emulator tailored for `240x320` non-touch displays, integrated into **System menu -> Games** and **File Manager (`xfiles`)** for `.gb` and `.gbc` files.
- Readjustable keypad controls persisted in `~/.config/sxmo/gambatte.cfg` with defaults `B = 8`, `Turbo B = 5`, `A = 9`, `Turbo A = 6`, `Directions = D-Pad`, `Start = 2`, `Select = 3`, `Palette = *`, and `Fast Forward = 0`.
- 10 built-in DMG color palettes, 4 screen scaling modes (`Fit 240x216` 1.5x area-interpolated, `1:1 160x144`, `Fill 240x246`, `Full 240x296`), 10 save-state slots (`.st0`–`.st9`), automatic battery SRAM/RTC persistence, seamless `Xdrawer` focus handoff, and direct low-latency `libasound` (`hw:0,0`) audio with hardware volume synchronization and analog-stage low-pass filtering.
- Preinstalled open-source / public-domain GB and GBC games in `/usr/share/games/gb/` and `~/Games/` (`2048.gb`, `adjustris.gb`, `blastah.gb`, `brickster.gbc`, `geometrix.gbc`, `ucity.gbc`).
- **Preinstalled scripting and calculation tools**:
- Added **Lua** (`lua5.5` and `lua5.4` with `/usr/bin/lua` and `/usr/bin/luac`), **GNU `bc` / `dc`**, and **Yabasic (`yabasic`)** to the default system image.
---
## Build `a1025` — codename `argonaut` (2026-10-25) ## Build `a1025` — codename `argonaut` (2026-10-25)
Initial public release of **Bananarchy Linux**, focused on complete hardware and daily-driver software support for the **Nokia 8110 4G (`nokia-argon`)**, along with initial build targets for the **Nokia 2720 Flip (`nokia-beatles`)**, **Nokia 800 Tough (`nokia-tough`)**, **Nokia 8000 4G (`nokia-sparkler`)**, and **Nokia 6300 4G (`nokia-leo`)**. Initial public release of **Bananarchy Linux**, focused on complete hardware and daily-driver software support for the **Nokia 8110 4G (`nokia-argon`)**, along with initial build targets for the **Nokia 2720 Flip (`nokia-beatles`)**, **Nokia 800 Tough (`nokia-tough`)**, **Nokia 8000 4G (`nokia-sparkler`)**, and **Nokia 6300 4G (`nokia-leo`)**.
+23 -7
View File
@@ -34,7 +34,7 @@ This manual covers flashing, operating, and customizing **Bananarchy Linux** (`2
- **QDSP6 / Q6Voice audio and Dual-SIM modem**: loudspeaker, boosted (+6 dB) earpiece, internal MEMS and headset microphones, `ModemManager` + `q6voiced` call audio routing, and direct Qualcomm QMI UIM Dual-SIM slot switching, PIN/PUK management, and SIM phonebook access. - **QDSP6 / Q6Voice audio and Dual-SIM modem**: loudspeaker, boosted (+6 dB) earpiece, internal MEMS and headset microphones, `ModemManager` + `q6voiced` call audio routing, and direct Qualcomm QMI UIM Dual-SIM slot switching, PIN/PUK management, and SIM phonebook access.
- **Rear 2MP camera and torch LED**: `msm8905-cam` V4L2 driver (`/dev/video0` `800x600` `RGB3` for GalaxyCore `GC2395`) and `leds-msm8905-torch` (`white:torch` + PM8909 clean poweroff). - **Rear 2MP camera and torch LED**: `msm8905-cam` V4L2 driver (`/dev/video0` `800x600` `RGB3` for GalaxyCore `GC2395`) and `leds-msm8905-torch` (`white:torch` + PM8909 clean poweroff).
5. **Preinstalled developer and utility stack**: 5. **Preinstalled developer and utility stack**:
- `gcc`/`g++`, `make`, `git`, GNU `coreutils`, `nano`, `ed`, `less`, `rlwrap`, `janet` + `jpm`, `bantam` coding agent, `perl` (`IO::Socket::SSL`), `tcl` (`tcllib`, `tcl-tls`, `sqlite-tcl`), `sqlite3`, `frotz`, `ffmpeg`, `mpv`, `imv`, and standalone `snake` and `tetris` X11 games. - `gcc`/`g++`, `make`, `git`, GNU `coreutils`, GNU `bc`/`dc`, `nano`, `ed`, `less`, `rlwrap`, `lua` (`5.5` and `5.4`), `yabasic`, `janet` + `jpm`, `bantam` coding agent, `perl` (`IO::Socket::SSL`), `tcl` (`tcllib`, `tcl-tls`, `sqlite-tcl`), `sqlite3`, `frotz`, `ffmpeg`, `mpv`, `imv`, standalone `snake` and `tetris` X11 games, and the `gambatte` Game Boy / Game Boy Color emulator.
--- ---
@@ -138,9 +138,10 @@ All fifteen phone applications are compiled as shared-object plugins (`/usr/lib/
- Typing **`*#06#`** immediately opens the **Device IMEI (`*#06#`)** dialog showing `IMEI 1`, `IMEI 2`, and `IMEI SV`. - Typing **`*#06#`** immediately opens the **Device IMEI (`*#06#`)** dialog showing `IMEI 1`, `IMEI 2`, and `IMEI SV`.
- Typing any USSD code (`*...#` or `#...#`, such as `*100#`) switches the center softkey to **`SEND`**. Pressing **Call (green)** or **Center `OK`** runs the USSD session inside an interactive in-dialer modal with scrollable carrier output and multi-step reply support. - Typing any USSD code (`*...#` or `#...#`, such as `*100#`) switches the center softkey to **`SEND`**. Pressing **Call (green)** or **Center `OK`** runs the USSD session inside an interactive in-dialer modal with scrollable carrier output and multi-step reply support.
4. **Call history (`calllog.so` / `xcalllog`)**: 4. **Call history and anti-spam blocklist (`calllog.so` / `xcalllog`)**:
- **Open**: press **Call (green)** on the empty desktop, or select **Call Log** from the System menu or Dialer. - **Open**: press **Call (green)** on the empty desktop, or select **Call Log** from the System menu or Dialer.
- **Controls**: `Left`/`Right` switches between **`All`**, **`Missed`**, **`In`**, and **`Out`** tabs; `Up`/`Down` scrolls; **Call (green)** or **Center `OK`** dials the selected entry; **Left softkey (`Dialer`)** loads the number into the Dialer. - **Controls**: `Left`/`Right` switches between **`All`**, **`Miss`**, **`In`**, **`Out`**, and **`Spam`** tabs; `Up`/`Down` scrolls; **Call (green)** or **Center `OK`** dials the selected entry.
- **Anti-spam blocklist (`~/.config/sxmo/block.tsv`)**: press **Left softkey (`Options`)** to **Block Number (Spam)** or **Unblock Number**, open **Blocklist (N)** to manage blocked numbers, **Block a Number...** (supports exact numbers, 9-digit suffix matching, or prefix wildcards ending with `*`), or **Clear Spam Log** (`~/.local/share/sxmo/block/modemlog.tsv`). Incoming calls from blocked numbers are automatically hung up and recorded in the `Spam` tab without triggering missed-call notifications.
5. **Incoming and active call screen (`callscreen.so` / `xcallscreen`)**: 5. **Incoming and active call screen (`callscreen.so` / `xcallscreen`)**:
- **Automatic activation**: wakes the screen and opens automatically on incoming and outgoing calls. Pressing **Call (green)** during an active call reopens `xcallscreen` from anywhere. - **Automatic activation**: wakes the screen and opens automatically on incoming and outgoing calls. Pressing **Call (green)** during an active call reopens `xcallscreen` from anywhere.
@@ -159,14 +160,16 @@ All fifteen phone applications are compiled as shared-object plugins (`/usr/lib/
- **Direct Dual-SIM QMI phonebook import and export**: select **Import from SIM** or **Export to SIM** in the **Options** menu (or **Copy to SIM** on an individual contact) to read or write `EF_ADN` SIM phonebook records directly over Qualcomm QMI UIM (`qrtr://0`) on either **SIM 1** or **SIM 2** — even on the inactive SIM slot. - **Direct Dual-SIM QMI phonebook import and export**: select **Import from SIM** or **Export to SIM** in the **Options** menu (or **Copy to SIM** on an individual contact) to read or write `EF_ADN` SIM phonebook records directly over Qualcomm QMI UIM (`qrtr://0`) on either **SIM 1** or **SIM 2** — even on the inactive SIM slot.
- **Built-in contact editor**: create or edit contacts directly on the keypad (`#` cycles input mode, `*` inserts space, hold `0` or press `Right` for `+`, or pick from recent unknown callers). - **Built-in contact editor**: create or edit contacts directly on the keypad (`#` cycles input mode, `*` inserts space, hold `0` or press `Right` for `+`, or pick from recent unknown callers).
7. **SMS manager and composer (`sms.so` / `xsms`)**: 7. **SMS manager, composer, and spam filter (`sms.so` / `xsms`)**:
- **Open**: select **Texts** from the System menu, or choose **Send SMS** / **Read Messages** from Contacts. - **Open**: select **Texts** from the System menu, or choose **Send SMS** / **Read Messages** from Contacts.
- **Conversations and thread view**: browse SMS threads and drafts with contact name resolution, unread indicators, and message bubbles (`Recv` and `Sent`). - **Conversations, Inbox, and Spam Folder**: browse SMS threads and drafts with contact name resolution, unread indicators, and message bubbles (`Recv` and `Sent`). Press `Left`/`Right` in the thread list (or use **Left softkey (`Options`)**) to switch between **`Inbox`** (`~/.local/share/sxmo/modem`) and the **`Spam`** folder (`~/.local/share/sxmo/block`).
- **Anti-spam blocking**: press **Left softkey (`Options`)** on any thread or conversation to **Block Number (Spam)** (which adds the sender to `~/.config/sxmo/block.tsv` and moves their thread into the `Spam` folder), **Unblock & Restore** (which unblocks the sender and restores their thread to `Inbox`), **Manage Blocklist (N)**, or **Block a Number...**. Incoming texts from blocked numbers are silently routed into `~/.local/share/sxmo/block` without ringing or vibrating.
- **Multi-tap composer**: pick a recipient from Contacts/Recents or type a number (hold `0` for `+`), compose messages with multi-tap keypad entry (`#` cycles input mode), monitor live character/segment counts (`0/160`), save drafts automatically, and send via **Call (green)**, **Center `OK`**, or **Left softkey**. - **Multi-tap composer**: pick a recipient from Contacts/Recents or type a number (hold `0` for `+`), compose messages with multi-tap keypad entry (`#` cycles input mode), monitor live character/segment counts (`0/160`), save drafts automatically, and send via **Call (green)**, **Center `OK`**, or **Left softkey**.
8. **File manager (`files.so` / `xfiles`)**: 8. **File manager (`files.so` / `xfiles`)**:
- **Open**: select **Files** from the System menu or press `Mod4+d`. - **Open**: select **Files** from the System menu or press `Mod4+d`.
- **Controls and viewers**: navigate directories with `Up`/`Down`/`Left`/`Right`/`Return`, filter with `0`–`9`, toggle dotfiles with `*`, and cycle sort order (**Name** / **Date** / **Size**) with `#`. - **Controls and viewers**: navigate directories with `Up`/`Down`/`Left`/`Right`/`Return`, filter with `0`–`9`, toggle dotfiles with `*`, and cycle sort order (**Name** / **Date** / **Size**) with `#`.
- **Game Boy / Game Boy Color ROM launching**: pressing `Return` on any `.gb` or `.gbc` file (or selecting **Play in Game Boy Emulator** from the **Left softkey (`Actions`)** menu) opens it directly in the **Gambatte (`gambatte`)** emulator.
- **Image preview and wallpaper installation**: pressing `Return` on any image file (`.jpg`, `.png`, `.bmp`, `.gif`, `.webp`, `.ppm`, `.tif`, `.ico`, `.svg`) opens the built-in image viewer (`*` toggles full-screen preview; **Left softkey (`Set Wall`)** installs the image into `~/Pictures/Wallpapers/`, applies it via `feh`, and refreshes Conky). You can also select **Preview Image** or **Set as Wallpaper** directly from the **Left softkey (`Actions`)** menu. - **Image preview and wallpaper installation**: pressing `Return` on any image file (`.jpg`, `.png`, `.bmp`, `.gif`, `.webp`, `.ppm`, `.tif`, `.ico`, `.svg`) opens the built-in image viewer (`*` toggles full-screen preview; **Left softkey (`Set Wall`)** installs the image into `~/Pictures/Wallpapers/`, applies it via `feh`, and refreshes Conky). You can also select **Preview Image** or **Set as Wallpaper** directly from the **Left softkey (`Actions`)** menu.
- **Audio playback and ringtone installation**: pressing `Return` on any audio or tracker module file opens it immediately in **Music (`xmusic`)**. From the **Left softkey (`Actions`)** menu on any audio/tracker file, select **Play in Music Player** to play it in `xmusic`, or **Set as Ringtone** to copy it to `~/Ringtones/` and set it as the active phone ringtone. - **Audio playback and ringtone installation**: pressing `Return` on any audio or tracker module file opens it immediately in **Music (`xmusic`)**. From the **Left softkey (`Actions`)** menu on any audio/tracker file, select **Play in Music Player** to play it in `xmusic`, or **Set as Ringtone** to copy it to `~/Ringtones/` and set it as the active phone ringtone.
- **File management actions**: the **Left softkey (`Actions`)** menu also provides **View Text**, **Edit in Nano**, **New Folder**, **New File**, **Rename**, **Copy**, **Cut / Move**, **Paste Here**, **Properties**, **Open Terminal Here**, and **Delete**. - **File management actions**: the **Left softkey (`Actions`)** menu also provides **View Text**, **Edit in Nano**, **New Folder**, **New File**, **Rename**, **Copy**, **Cut / Move**, **Paste Here**, **Properties**, **Open Terminal Here**, and **Delete**.
@@ -210,10 +213,23 @@ All fifteen phone applications are compiled as shared-object plugins (`/usr/lib/
- **`2:Queue`**: browse the active playlist queue, filter with `0`–`9`, press `Return` to jump to a track, `*` to remove a track, `#` to shuffle, or **Left softkey (`Actions`)** to set the selected track as ringtone or clear the queue. - **`2:Queue`**: browse the active playlist queue, filter with `0`–`9`, press `Return` to jump to a track, `*` to remove a track, `#` to shuffle, or **Left softkey (`Actions`)** to set the selected track as ringtone or clear the queue.
- **`3:Files`**: browse the filesystem (`~/Music`, `/usr/share/sxmo/music`, `/usr/share/sxmo/ringtones`, or any folder), press `Return` to play a file and load its folder into the queue, `*` to append a file or folder to the queue, or `#` to play the entire current folder. - **`3:Files`**: browse the filesystem (`~/Music`, `/usr/share/sxmo/music`, `/usr/share/sxmo/ringtones`, or any folder), press `Return` to play a file and load its folder into the queue, `*` to append a file or folder to the queue, or `#` to play the entire current folder.
#### 3.1.3 Standalone keypad games (`Snake` and `Tetris`) #### 3.1.3 Standalone keypad games (`Snake`, `Tetris`) and Game Boy / Game Boy Color emulator (`Gambatte`)
Two classic X11 games (`/usr/bin/snake` and `/usr/bin/tetris`) are accessible from **System menu -> Games**: Two classic X11 games (`/usr/bin/snake` and `/usr/bin/tetris`) and the full-featured **Gambatte (`gambatte` / `gbemu`)** Game Boy / Game Boy Color emulator are accessible from **System menu -> Games** (or by opening any `.gb` / `.gbc` file directly in **Files (`xfiles`)**):
- **Snake (`snake`)**: steer with `2`/`8`/`4`/`6` or D-Pad; pause or restart with `5`/`Return`/`#`/`*`; quit with `Right softkey`/`BackSpace`/`Escape`. - **Snake (`snake`)**: steer with `2`/`8`/`4`/`6` or D-Pad; pause or restart with `5`/`Return`/`#`/`*`; quit with `Right softkey`/`BackSpace`/`Escape`.
- **Tetris (`tetris`)**: move with `4`/`6` (`Left`/`Right`), rotate with `2` (`Up`), soft drop with `8` (`Down`), hard drop with `0`/`5`/`Return`, pause with `#`/`*`, and quit with `Right softkey`/`BackSpace`/`Escape`. - **Tetris (`tetris`)**: move with `4`/`6` (`Left`/`Right`), rotate with `2` (`Up`), soft drop with `8` (`Down`), hard drop with `0`/`5`/`Return`, pause with `#`/`*`, and quit with `Right softkey`/`BackSpace`/`Escape`.
- **Game Boy / Game Boy Color emulator (`gambatte` / `gbemu`)**:
- **Launch**: open **Game Boy (Gambatte)** from **System menu -> Games**, open any `.gb` or `.gbc` file from **Files (`xfiles`)**, or run `gambatte [rom.gb|rom.gbc]`. When launched without a file argument, opens a built-in ROM browser scanning `/usr/share/games/gb`, `~/Games`, `~/ROMs`, `~/Downloads`, and user directories.
- **Default keypad controls** (fully readjustable via **Pause Menu -> Keypad Controls...** or **Left softkey (`Controls`)** in the ROM browser; persisted in `~/.config/sxmo/gambatte.cfg`):
- **B Button**: `8`
- **Turbo B**: `5`
- **A Button**: `9`
- **Turbo A**: `6`
- **Directions (`Up`/`Down`/`Left`/`Right`)**: `D-Pad` (`Up`, `Down`, `Left`, `Right`)
- **Start**: `2`
- **Select**: `3`
- **Palette Selection**: `*` (cycles through 10 built-in color palettes: `DMG Classic`, `Bananarchy Gold`, `GB Pocket Gray`, `GB Light Teal`, `GBC Blue/Red`, `Super Game Boy`, `Amber CRT`, `Mintberry`, `Virtual Boy`, `Inverted Dark`)
- **Fast Forward (2x)**: `0` (while held)
- **Quick Shortcuts (when unbound)**: `1` cycles screen scaling mode (`Fit 240x216` 1.5x area-interpolated, `1:1 160x144`, `Fill 240x246`, `Full 240x296`); `4` saves state to the active slot; `7` loads state from the active slot; `#`, `BackSpace`, `Escape`, or either softkey opens the **Gambatte Pause Menu** (Save/Load State across 10 slots `.st0`..`.st9`, Palette, Screen Scale, Audio toggle, Keypad Controls remapping, Load Another ROM, and Quit). Battery-backed SRAM (`.sav`) and RTC (`.rtc`) are saved automatically in `~/.local/share/sxmo/gambatte/saves/`.
### 3.2 Multi-mode `fcitx5-t9` keypad reference ### 3.2 Multi-mode `fcitx5-t9` keypad reference
+6 -6
View File
@@ -22,17 +22,17 @@
Instead of spawning slow shell scripts for everyday phone tasks, Bananarchy runs a suite of preloaded C/C++ plugins inside DWM that sleep at 0% CPU and open in `< 2 ms`: Instead of spawning slow shell scripts for everyday phone tasks, Bananarchy runs a suite of preloaded C/C++ plugins inside DWM that sleep at 0% CPU and open in `< 2 ms`:
- **System and context menus (`dmenu`)**: instant full-screen 10-line vertical menus with live T9 filtering, built-in submenus, and hardware toggles. - **System and context menus (`dmenu`)**: instant full-screen 10-line vertical menus with live T9 filtering, built-in submenus, and hardware toggles.
- **Global controls drawer (`xdrawer`)**: press **both softkeys together** from anywhere to adjust speaker, earpiece, and ringtone volume, ringer mode (`Ring+Vib` / `Vibrate` / `Silent` / `DND`), 3-level vibration intensity (`Light` / `Medium` / `Strong`), flashlight, sound and microphone mute, screen brightness, and keypad backlight. - **Global controls drawer (`xdrawer`)**: press **both softkeys together** from anywhere to adjust speaker, earpiece, and ringtone volume, ringer mode (`Ring+Vib` / `Vibrate` / `Silent` / `DND`), 3-level vibration intensity (`Light` / `Medium` / `Strong`), flashlight, sound and microphone mute, screen brightness, and keypad backlight.
- **Phone dialer (`xdialer`), call log (`xcalllog`), and call screen (`xcallscreen`)**: press any digit on the home screen to start dialing with live T9 contact matching, run interactive carrier USSD codes (`*100#`, etc.), or type `*#06#` to inspect both IMEIs. Incoming and active calls open a dedicated call screen with earpiece/speaker switching, microphone mute, DTMF keypad, and one-key normalized WAV call recording (`~/Recordings/rec_HHMMSS_<caller>.wav`). - **Phone dialer (`xdialer`), call log (`xcalllog`), and call screen (`xcallscreen`)**: press any digit on the home screen to start dialing with live T9 contact matching, run interactive carrier USSD codes (`*100#`, etc.), or type `*#06#` to inspect both IMEIs. Incoming and active calls open a dedicated call screen with earpiece/speaker switching, microphone mute, DTMF keypad, and one-key normalized WAV call recording (`~/Recordings/rec_HHMMSS_<caller>.wav`). The Call Log includes a 5-tab view (`All`, `Miss`, `In`, `Out`, `Spam`) with integrated anti-spam number blocking (`~/.config/sxmo/block.tsv`) that automatically hangs up blocked callers and logs them to the `Spam` tab without triggering missed-call notifications.
- **Contacts (`xcontacts`) and SMS manager (`xsms`)**: full phonebook editor with direct Qualcomm QMI SIM card contact import and export across SIM 1 and SIM 2, paired with a threaded SMS conversation view and multi-tap message composer. - **Contacts (`xcontacts`) and SMS manager (`xsms`)**: full phonebook editor with direct Qualcomm QMI SIM card contact import and export across SIM 1 and SIM 2, paired with a threaded SMS conversation view, multi-tap message composer, and built-in anti-spam blocklist / `Spam Folder` (`~/.local/share/sxmo/block`) with one-key thread blocking and restoration.
- **Modem and Dual-SIM manager (`xmodem`)**: 4-tab cellular control center (`SIMs`, `Radio`, `AT`, `Log`) supporting physical SIM 1 / SIM 2 active slot switching, SIM PIN unlock and PUK unblock wizards, 2G/3G/4G radio mode selection, interactive USSD sessions, and a live AT command console. - **Modem and Dual-SIM manager (`xmodem`)**: 4-tab cellular control center (`SIMs`, `Radio`, `AT`, `Log`) supporting physical SIM 1 / SIM 2 active slot switching, SIM PIN unlock and PUK unblock wizards, 2G/3G/4G radio mode selection, interactive USSD sessions, and a live AT command console.
- **Networks and Bluetooth manager (`xnetworks`)**: 3-tab connectivity manager for Wi-Fi networks, WPA2 password entry, Wi-Fi hotspot, USB tethering, 4G/LTE mobile data (`wwan0`) and APN editing, and Bluetooth pairing. - **Networks and Bluetooth manager (`xnetworks`)**: 3-tab connectivity manager for Wi-Fi networks, WPA2 password entry, Wi-Fi hotspot, USB tethering, 4G/LTE mobile data (`wwan0`) and APN editing, and Bluetooth pairing.
- **File manager (`xfiles`), music and tracker player (`xmusic`), and camera (`xcamera`)**: keypad directory browser with built-in text and image viewers (plus one-click actions to play audio, preview images, set any image as wallpaper, or install any audio or tracker file as ringtone), a 3-tab music and chiptune tracker module player (`WAV`, `MP3`, `OGG`, `FLAC`, `Opus`, `MOD`, `XM`, `IT`, `S3M`, and more), and a live V4L2 camera viewfinder with digital zoom, flash/torch control, self-timer, and photo gallery. - **File manager (`xfiles`), music and tracker player (`xmusic`), and camera (`xcamera`)**: keypad directory browser with built-in text and image viewers (plus one-click actions to launch `.gb`/`.gbc` ROMs in the Game Boy emulator, play audio, preview images, set any image as wallpaper, or install any audio or tracker file as ringtone), a 3-tab music and chiptune tracker module player (`WAV`, `MP3`, `OGG`, `FLAC`, `Opus`, `MOD`, `XM`, `IT`, `S3M`, and more), and a live V4L2 camera viewfinder with digital zoom, flash/torch control, self-timer, and photo gallery.
- **Themes (`xthemes`), wallpapers (`xwallpapers`), and ringtones (`xringtones`)**: installable `.Xresources` color and font themes (`~/.xthemes/*.xth`, 20 preinstalled schemes) with live zero-restart reload across the entire UI and terminal, alongside dedicated managers for desktop wallpapers and call ringtones (supporting both standard audio and tracker modules) with 3-level vibration control. - **Themes (`xthemes`), wallpapers (`xwallpapers`), and ringtones (`xringtones`)**: installable `.Xresources` color and font themes (`~/.xthemes/*.xth`, 20 preinstalled schemes) with live zero-restart reload across the entire UI and terminal, alongside dedicated managers for desktop wallpapers and call ringtones (supporting both standard audio and tracker modules) with 3-level vibration control.
### Input, terminal, and developer tools ### Input, terminal, and developer tools
- **6-mode `fcitx5-t9` keypad input**: press `#` to cycle between `abc`, `Abc`, `ABC`, `Sym` (all 32 ASCII punctuation symbols), `Ctl` (`Ctrl` sequences, arrows, `PageUp`/`PageDown`, `F1`–`F12`), and `123` numeric modes with a fixed bottom candidate bar. - **6-mode `fcitx5-t9` keypad input**: press `#` to cycle between `abc`, `Abc`, `ABC`, `Sym` (all 32 ASCII punctuation symbols), `Ctl` (`Ctrl` sequences, arrows, `PageUp`/`PageDown`, `F1`–`F12`), and `123` numeric modes with a fixed bottom candidate bar.
- **First-class Linux terminal (`st`)**: patched with HarfBuzz OpenType ligatures (`Fira Code` as the default system and terminal font), XIM T9 input, and live theme reloading. - **First-class Linux terminal (`st`)**: patched with HarfBuzz OpenType ligatures (`Fira Code` as the default system and terminal font), XIM T9 input, and live theme reloading.
- **On-device development and AI**: preinstalled `gcc`/`g++`, `make`, `git`, GNU `coreutils`, `nano`, `ed`, `sqlite3`, `perl`, `tcl`, the [Janet](https://janet-lang.org/) language and `jpm`, the [Bantam](https://bantam.luxferre.top/) coding agent, `frotz` (interactive fiction), and standalone X11 keypad games (`Snake` and `Tetris`). - **On-device development, scripting, and gaming**: preinstalled `gcc`/`g++`, `make`, `git`, GNU `coreutils`, GNU `bc`/`dc`, `nano`, `ed`, `sqlite3`, `lua` (`5.5` and `5.4`), `yabasic`, `perl`, `tcl`, the [Janet](https://janet-lang.org/) language and `jpm`, the [Bantam](https://bantam.luxferre.top/) coding agent, `frotz` (interactive fiction), standalone X11 keypad games (`Snake` and `Tetris`), and a full-featured Gambatte-based Game Boy / Game Boy Color emulator (`gambatte` / `gbemu`) with readjustable keypad controls (`B=8`, `Turbo B=5`, `A=9`, `Turbo A=6`, `D-pad`, `Start=2`, `Select=3`, `Palette=*`), 10 color palettes, save states, and File Manager `.gb`/`.gbc` integration.
--- ---
@@ -74,7 +74,7 @@ bananarchy/
│ ├── xf86-input-libinput/ # Xlibre xf86-input-libinput DDX driver │ ├── xf86-input-libinput/ # Xlibre xf86-input-libinput DDX driver
│ ├── xf86-video-fbdev/ # Xlibre xf86-video-fbdev fallback DDX driver │ ├── xf86-video-fbdev/ # Xlibre xf86-video-fbdev fallback DDX driver
│ ├── fcitx5-t9/ # 6-mode fcitx5-t9 keypad input method │ ├── fcitx5-t9/ # 6-mode fcitx5-t9 keypad input method
│ ├── games/ # Standalone keypad X11 games (snake.cpp, tetris.cpp) │ ├── games/ # Standalone keypad X11 games and Gambatte GB/GBC emulator (snake, tetris, gambatte)
│ ├── leds-msm8905-torch/ # Kernel driver for SY7807 torch LED, WCD codec init, and PON shutdown │ ├── leds-msm8905-torch/ # Kernel driver for SY7807 torch LED, WCD codec init, and PON shutdown
│ └── msm8905-cam/ # Kernel V4L2 driver for GC2395 2MP rear camera (/dev/video0) │ └── msm8905-cam/ # Kernel V4L2 driver for GC2395 2MP rear camera (/dev/video0)
├── overlay/rootfs/ # System configuration overlay applied over the target rootfs ├── overlay/rootfs/ # System configuration overlay applied over the target rootfs
@@ -101,7 +101,7 @@ All components are pre-compiled in `prebuilt/` and `overlay/rootfs/`, and can be
# Rebuild patched sxmo-st, sxmo-dmenu, sxmo-dwm, and all 14 C++ DWM plugins # Rebuild patched sxmo-st, sxmo-dmenu, sxmo-dwm, and all 14 C++ DWM plugins
./scripts/build-sxmo-x11.sh ./scripts/build-sxmo-x11.sh
# Rebuild standalone X11 games (snake, tetris) # Rebuild standalone X11 games and emulators (snake, tetris, gambatte)
./scripts/build-games.sh ./scripts/build-games.sh
# Rebuild multi-mode fcitx5-t9 input engine # Rebuild multi-mode fcitx5-t9 input engine
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
gambatte
+1
View File
@@ -0,0 +1 @@
lua5.5
+1
View File
@@ -0,0 +1 @@
luac5.5
+8 -1
View File
@@ -81,6 +81,11 @@ checkforfinishedcalls() {
sxmo_notify_user.sh "Call with $CONTACT terminated" sxmo_notify_user.sh "Call with $CONTACT terminated"
stderr "Finished call from $FINISHEDNUMBER" stderr "Finished call from $FINISHEDNUMBER"
printf %b "$TIME\tcall_finished\t$FINISHEDNUMBER\n" >> "$SXMO_LOGDIR/modemlog.tsv" printf %b "$TIME\tcall_finished\t$FINISHEDNUMBER\n" >> "$SXMO_LOGDIR/modemlog.tsv"
elif [ -f "$XDG_RUNTIME_DIR/sxmo_calls/${FINISHEDCALLID}.blockedcall" ]; then
STATE=muted
mkdir -p "$SXMO_BLOCKDIR"
stderr "Blocked call finished from $FINISHEDNUMBER"
printf %b "$TIME\tcall_blocked\t$FINISHEDNUMBER\n" >> "$SXMO_BLOCKDIR/modemlog.tsv"
elif [ -f "$XDG_RUNTIME_DIR/sxmo_calls/${FINISHEDCALLID}.mutedring" ]; then elif [ -f "$XDG_RUNTIME_DIR/sxmo_calls/${FINISHEDCALLID}.mutedring" ]; then
STATE=muted STATE=muted
#this ring was muted up #this ring was muted up
@@ -153,6 +158,8 @@ checkforincomingcalls() {
TIME="$(date +%FT%H:%M:%S%z)" TIME="$(date +%FT%H:%M:%S%z)"
if sxmo_hook_block_call.sh "$INCOMINGNUMBER" 2>/dev/null; then if sxmo_hook_block_call.sh "$INCOMINGNUMBER" 2>/dev/null; then
stderr "BLOCKED call from number: $VOICECALLID" stderr "BLOCKED call from number: $VOICECALLID"
mkdir -p "$SXMO_BLOCKDIR"
touch "$XDG_RUNTIME_DIR/sxmo_calls/${VOICECALLID}.blockedcall"
sxmo_modemcall.sh mute "$VOICECALLID" sxmo_modemcall.sh mute "$VOICECALLID"
printf %b "$TIME\tcall_ring\t$INCOMINGNUMBER\n" >> "$SXMO_BLOCKDIR/modemlog.tsv" printf %b "$TIME\tcall_ring\t$INCOMINGNUMBER\n" >> "$SXMO_BLOCKDIR/modemlog.tsv"
else else
@@ -243,7 +250,7 @@ checkfornewtexts() {
TIME="$(printf %s "$TEXTDATA" | jq -r .sms.properties.timestamp)" TIME="$(printf %s "$TEXTDATA" | jq -r .sms.properties.timestamp)"
TIME="$(date +%FT%H:%M:%S%z -d "$TIME")" TIME="$(date +%FT%H:%M:%S%z -d "$TIME")"
if cut -f1 "$SXMO_BLOCKFILE" 2>/dev/null | grep -q "^$NUM$"; then if sxmo_hook_block_call.sh "$NUM" 2>/dev/null; then
mkdir -p "$SXMO_BLOCKDIR/$NUM" mkdir -p "$SXMO_BLOCKDIR/$NUM"
stderr "BLOCKED text from number: $NUM (TEXTID: $TEXTID)" stderr "BLOCKED text from number: $NUM (TEXTID: $TEXTID)"
sxmo_hook_smslog.sh "recv" "$NUM" "$NUM" "$TIME" "$TEXT" >> "$SXMO_BLOCKDIR/$NUM/sms.txt" sxmo_hook_smslog.sh "recv" "$NUM" "$NUM" "$TIME" "$TEXT" >> "$SXMO_BLOCKDIR/$NUM/sms.txt"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,57 @@
#!/bin/sh #!/bin/sh
# configversion: 4408f29a464ab22d7c978fa0475cf80e # configversion: 4408f29a464ab22d7c978fa0475cf80e
# This hook is executed to check if a call should be blocked # This hook is executed to check if a call or SMS should be blocked.
# If the hook return 0 the call will be blocked # If the hook returns 0 the call/SMS will be blocked.
INCOMINGNUMBER="$1" INCOMINGNUMBER="$1"
BLOCKFILE="${SXMO_BLOCKFILE:-${XDG_CONFIG_HOME:-$HOME/.config}/sxmo/block.tsv}"
cut -f1 "$SXMO_BLOCKFILE" 2>/dev/null | grep -q "^$INCOMINGNUMBER$" [ -z "$INCOMINGNUMBER" ] && exit 1
[ ! -f "$BLOCKFILE" ] && exit 1
norm_phone() {
printf '%s' "$1" | tr -cd '0-9+*#'
}
suffix_key() {
digits="$(printf '%s' "$1" | tr -cd '0-9')"
len="${#digits}"
if [ "$len" -ge 9 ]; then
printf '%s' "$digits" | tail -c 9
else
printf '%s' "$digits"
fi
}
IN_NORM="$(norm_phone "$INCOMINGNUMBER")"
[ -z "$IN_NORM" ] && exit 1
IN_SFX="$(suffix_key "$IN_NORM")"
while IFS="$(printf '\t')" read -r pat _rest || [ -n "$pat" ]; do
[ -z "$pat" ] && continue
PAT_NORM="$(norm_phone "$pat")"
[ -z "$PAT_NORM" ] && continue
if [ "$PAT_NORM" = "$IN_NORM" ]; then
exit 0
fi
case "$PAT_NORM" in
*\*)
PREFIX="${PAT_NORM%\*}"
if [ -n "$PREFIX" ]; then
case "$IN_NORM" in
"$PREFIX"*) exit 0 ;;
esac
fi
;;
esac
PAT_SFX="$(suffix_key "$PAT_NORM")"
if [ "${#IN_SFX}" -ge 7 ] && [ "$PAT_SFX" = "$IN_SFX" ]; then
exit 0
fi
done < "$BLOCKFILE"
exit 1
@@ -33,3 +33,4 @@ write_line_app() {
write_line_app snake "$icon_gam Snake" "snake" write_line_app snake "$icon_gam Snake" "snake"
write_line_app tetris "$icon_gam Tetris" "tetris" write_line_app tetris "$icon_gam Tetris" "tetris"
write_line_app gambatte "$icon_gam Game Boy (Gambatte)" "gambatte"
BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
gambatte
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+27 -5
View File
@@ -30,7 +30,7 @@ EOF
binutils-armv7 \ binutils-armv7 \
pkgconf >/dev/null pkgconf >/dev/null
echo "[2/4] Preparing armv7 sysroot (X11)..." echo "[2/4] Preparing armv7 sysroot (X11 + ALSA)..."
SYSROOT="/tmp/armv7-sysroot" SYSROOT="/tmp/armv7-sysroot"
mkdir -p "$SYSROOT/etc/apk/keys" mkdir -p "$SYSROOT/etc/apk/keys"
cp /etc/apk/repositories "$SYSROOT/etc/apk/repositories" cp /etc/apk/repositories "$SYSROOT/etc/apk/repositories"
@@ -39,7 +39,8 @@ EOF
alpine-baselayout \ alpine-baselayout \
musl-dev \ musl-dev \
linux-headers \ linux-headers \
libx11-dev >/dev/null libx11-dev \
alsa-lib-dev >/dev/null
export PKG_CONFIG_SYSROOT_DIR="$SYSROOT" export PKG_CONFIG_SYSROOT_DIR="$SYSROOT"
export PKG_CONFIG_LIBDIR="$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig" export PKG_CONFIG_LIBDIR="$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig"
@@ -50,14 +51,35 @@ EOF
mkdir -p /bananarchy/prebuilt/games/usr/bin mkdir -p /bananarchy/prebuilt/games/usr/bin
echo "[3/4] Compiling Snake and Tetris..." echo "[3/4] Compiling Snake, Tetris, and Gambatte (Game Boy / Game Boy Color)..."
$CXX $OPT_CFLAGS -std=c++17 -Wall -Wextra \ $CXX $OPT_CFLAGS -std=c++17 -Wall -Wextra \
/bananarchy/src/games/snake.cpp $(pkgconf --libs x11) \ /bananarchy/src/games/snake.cpp $(pkgconf --libs x11) \
-o /bananarchy/prebuilt/games/usr/bin/snake -o /bananarchy/prebuilt/games/usr/bin/snake
$CXX $OPT_CFLAGS -std=c++17 -Wall -Wextra \ $CXX $OPT_CFLAGS -std=c++17 -Wall -Wextra \
/bananarchy/src/games/tetris.cpp $(pkgconf --libs x11) \ /bananarchy/src/games/tetris.cpp $(pkgconf --libs x11) \
-o /bananarchy/prebuilt/games/usr/bin/tetris -o /bananarchy/prebuilt/games/usr/bin/tetris
$STRIP --strip-unneeded /bananarchy/prebuilt/games/usr/bin/snake /bananarchy/prebuilt/games/usr/bin/tetris
GAMBATTE_DIR="/bananarchy/src/games/gambatte"
$CXX $OPT_CFLAGS -std=c++17 -DHAVE_STDINT_H -D__LIBRETRO__ \
$(pkgconf --cflags alsa) \
-I"$GAMBATTE_DIR" \
-I"$GAMBATTE_DIR/common" \
-I"$GAMBATTE_DIR/include" \
-I"$GAMBATTE_DIR/src" \
"$GAMBATTE_DIR"/src/*.cpp \
"$GAMBATTE_DIR"/src/mem/*.cpp \
"$GAMBATTE_DIR"/src/sound/*.cpp \
"$GAMBATTE_DIR"/src/video/*.cpp \
/bananarchy/src/games/gambatte_phone.cpp \
$(pkgconf --libs x11 alsa) -lpthread -lm \
-o /bananarchy/prebuilt/games/usr/bin/gambatte
ln -sf gambatte /bananarchy/prebuilt/games/usr/bin/gbemu
$STRIP --strip-unneeded \
/bananarchy/prebuilt/games/usr/bin/snake \
/bananarchy/prebuilt/games/usr/bin/tetris \
/bananarchy/prebuilt/games/usr/bin/gambatte
echo "[4/4] Installing into overlay and (optional) target rootfs..." echo "[4/4] Installing into overlay and (optional) target rootfs..."
mkdir -p /bananarchy/overlay/rootfs/usr/bin mkdir -p /bananarchy/overlay/rootfs/usr/bin
@@ -70,5 +92,5 @@ EOF
/bananarchy/prebuilt/games \ /bananarchy/prebuilt/games \
/bananarchy/overlay/rootfs/usr/bin \ /bananarchy/overlay/rootfs/usr/bin \
/bananarchy/src/games /bananarchy/src/games
ls -lh /bananarchy/prebuilt/games/usr/bin/snake /bananarchy/prebuilt/games/usr/bin/tetris ls -lh /bananarchy/prebuilt/games/usr/bin/snake /bananarchy/prebuilt/games/usr/bin/tetris /bananarchy/prebuilt/games/usr/bin/gambatte
' '
+9
View File
@@ -64,6 +64,7 @@ configure_common_rootfs() {
musl-dev \ musl-dev \
make \ make \
coreutils \ coreutils \
bc \
ed \ ed \
rlwrap \ rlwrap \
less \ less \
@@ -72,6 +73,11 @@ configure_common_rootfs() {
janet \ janet \
janet-dev \ janet-dev \
jpm \ jpm \
lua5.4 \
lua5.4-dev \
lua5.5 \
lua5.5-dev \
yabasic \
perl \ perl \
perl-io-socket-ssl \ perl-io-socket-ssl \
tcl \ tcl \
@@ -87,6 +93,9 @@ configure_common_rootfs() {
i2c-tools \ i2c-tools \
v4l-utils v4l-utils
ln -sf lua5.5 "$ROOTFS/usr/bin/lua"
ln -sf luac5.5 "$ROOTFS/usr/bin/luac"
echo " -> Installing busybox and busybox-extras symlinks..." echo " -> Installing busybox and busybox-extras symlinks..."
for applet in $(/bin/busybox --list-full); do for applet in $(/bin/busybox --list-full); do
target="$ROOTFS/$applet" target="$ROOTFS/$applet"
+661 -160
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0-only
// CallLog DWM Plugin - Preloaded resident X11 call log viewer & dialer launcher (240x320) // CallLog DWM Plugin - Preloaded resident X11 call log viewer, anti-spam blocklist & dialer launcher (240x320)
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
@@ -37,6 +37,20 @@ void handleSignal(int) {
gRunning = 0; gRunning = 0;
} }
enum class ViewMode {
Log = 0,
Options = 1,
Blocklist = 2,
AddBlock = 3,
};
struct OptionEntry {
int id;
std::string icon;
std::string label;
bool danger = false;
};
int calllog_run_main(int argc, char** argv) { int calllog_run_main(int argc, char** argv) {
gRunning = 1; gRunning = 1;
bool isPreload = false; bool isPreload = false;
@@ -116,13 +130,29 @@ int calllog_run_main(int argc, char** argv) {
XftColor& colRed = theme.colRed; XftColor& colRed = theme.colRed;
ContactBook book; ContactBook book;
BlockList blockList;
std::vector<CallLogItem> allLogs; std::vector<CallLogItem> allLogs;
std::vector<CallLogItem> blockedLogs;
std::vector<CallLogItem> filtered; std::vector<CallLogItem> filtered;
FileStamp contactsStamp; FileStamp contactsStamp;
FileStamp logStamp; FileStamp logStamp;
FileStamp blockFileStamp;
FileStamp blockLogStamp;
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out constexpr int kNumTabs = 5;
const char* kTabLabels[kNumTabs] = {"All", "Miss", "In", "Out", "Spam"};
ViewMode mode = ViewMode::Log;
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out, 4 = Spam/Blocked
int selected = 0; int selected = 0;
int optionSelected = 0;
int blockSelected = 0;
std::vector<OptionEntry> options;
std::string addBlockInput;
std::string toastMsg;
int64_t toastUntilMs = 0;
bool visible = false; bool visible = false;
int64_t openStartMs = 0; int64_t openStartMs = 0;
int64_t startupIgnoreUntilMs = 0; int64_t startupIgnoreUntilMs = 0;
@@ -130,13 +160,32 @@ int calllog_run_main(int argc, char** argv) {
int softkeyPending = 0; int softkeyPending = 0;
int64_t softkeyDeadlineMs = 0; int64_t softkeyDeadlineMs = 0;
auto showToast = [&](const std::string& msg) {
toastMsg = msg;
toastUntilMs = nowMs() + 1800;
};
auto applyFilter = [&]() { auto applyFilter = [&]() {
filtered.clear(); filtered.clear();
for (const auto& item : allLogs) { if (filterTab == 4) {
if (filterTab == 1 && item.direction != CallDirection::Missed) continue; for (auto item : blockedLogs) {
if (filterTab == 2 && item.direction != CallDirection::Incoming) continue; item.isBlockedNumber = blockList.isBlocked(item.number);
if (filterTab == 3 && item.direction != CallDirection::Outgoing) continue; filtered.push_back(item);
filtered.push_back(item); }
for (auto item : allLogs) {
if (blockList.isBlocked(item.number)) {
item.isBlockedNumber = true;
filtered.push_back(item);
}
}
} else {
for (auto item : allLogs) {
if (filterTab == 1 && item.direction != CallDirection::Missed) continue;
if (filterTab == 2 && item.direction != CallDirection::Incoming) continue;
if (filterTab == 3 && item.direction != CallDirection::Outgoing) continue;
item.isBlockedNumber = blockList.isBlocked(item.number);
filtered.push_back(item);
}
} }
if (selected >= static_cast<int>(filtered.size())) { if (selected >= static_cast<int>(filtered.size())) {
selected = std::max(0, static_cast<int>(filtered.size()) - 1); selected = std::max(0, static_cast<int>(filtered.size()) - 1);
@@ -144,21 +193,60 @@ int calllog_run_main(int argc, char** argv) {
}; };
bool logsLoadedOnce = false; bool logsLoadedOnce = false;
auto reloadLogsIfNeeded = [&]() -> bool { auto reloadLogsIfNeeded = [&](bool force = false) -> bool {
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath()); bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
bool lChanged = logStamp.checkAndUpdate(getModemLogFilePath()); bool lChanged = logStamp.checkAndUpdate(getModemLogFilePath());
if (cChanged || lChanged || !logsLoadedOnce) { bool bChanged = blockFileStamp.checkAndUpdate(getBlockFilePath());
bool blChanged = blockLogStamp.checkAndUpdate(getBlockedModemLogFilePath());
if (force || cChanged || lChanged || bChanged || blChanged || !logsLoadedOnce) {
logsLoadedOnce = true; logsLoadedOnce = true;
book.load(); book.load();
blockList.load();
allLogs = loadCallLog(book); allLogs = loadCallLog(book);
blockedLogs = loadBlockedCallLog(book);
applyFilter(); applyFilter();
if (blockSelected >= static_cast<int>(blockList.entries().size())) {
blockSelected = std::max(0, static_cast<int>(blockList.entries().size()) - 1);
}
return true; return true;
} }
return false; return false;
}; };
reloadLogsIfNeeded(); reloadLogsIfNeeded(true);
applyFilter();
auto buildOptions = [&]() {
options.clear();
bool hasSel = (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size()));
if (hasSel) {
const CallLogItem& item = filtered[selected];
bool isBlk = blockList.isBlocked(item.number);
options.push_back({1, kIconPhone, "Call Number", false});
options.push_back({2, kIconPhoneLog, "Open in Dialer", false});
options.push_back({3, kIconSms, "Send SMS", false});
if (isBlk) {
options.push_back({4, kIconCheck, "Unblock Number", false});
} else {
options.push_back({4, kIconBan, "Block Number (Spam)", true});
}
options.push_back({5, kIconUser, "Contacts", false});
} else {
options.push_back({2, kIconPhoneLog, "Open Dialer", false});
}
std::string blkCount =
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
options.push_back({6, kIconShield, blkCount, false});
options.push_back({7, kIconPlus, "Block a Number...", false});
if (filterTab == 4) {
if (!blockedLogs.empty()) {
options.push_back({8, kIconTrash, "Clear Spam Log", true});
}
} else if (!allLogs.empty()) {
options.push_back({9, kIconTrash, "Clear Call Log", true});
}
optionSelected = 0;
};
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h, auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
const std::string& text, int maxW = -1) { const std::string& text, int maxW = -1) {
@@ -191,8 +279,6 @@ int calllog_run_main(int argc, char** argv) {
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH; constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
constexpr int kVisibleRows = 5; constexpr int kVisibleRows = 5;
const char* kTabLabels[4] = {"All", "Missed", "In", "Out"};
auto render = [&]() { auto render = [&]() {
XSetForeground(dpy, gc, theme.spec.bgMain); XSetForeground(dpy, gc, theme.spec.bgMain);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
@@ -200,108 +286,270 @@ int calllog_run_main(int argc, char** argv) {
XSetForeground(dpy, gc, theme.spec.bgHeader); XSetForeground(dpy, gc, theme.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH);
for (int t = 0; t < 4; ++t) { std::string softLeft = "Options";
int tx = (t * kWinWidth) / 4; std::string softCenter = "CALL";
int tw = ((t + 1) * kWinWidth) / 4 - tx; std::string softRight = "Close";
bool active = (t == filterTab);
if (active) {
XSetForeground(dpy, gc, theme.spec.selBg);
XFillRectangle(dpy, pix, gc, tx, 0, tw, kHeaderH - 2);
XSetForeground(dpy, gc, theme.spec.accent);
XFillRectangle(dpy, pix, gc, tx, kHeaderH - 3, tw, 3);
}
std::string lbl(kTabLabels[t]);
int lw = pangoWidth(layoutSmall, lbl);
drawPango(layoutSmall, active ? &colWhite : &colMuted,
tx + (tw - lw) / 2, 0, kHeaderH - 2, lbl);
}
XSetForeground(dpy, gc, theme.spec.border); if (mode == ViewMode::Log) {
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1); for (int t = 0; t < kNumTabs; ++t) {
int tx = (t * kWinWidth) / kNumTabs;
if (filtered.empty()) { int tw = ((t + 1) * kWinWidth) / kNumTabs - tx;
drawPango(layoutIcon, &colMuted, 112, kListY + 60, 24, kIconPhoneLog); bool active = (t == filterTab);
drawPango(layoutMain, &colMuted, 52, kListY + 90, 28, "No call history", 160); if (active) {
drawPango(layoutSmall, &colMuted, 24, kListY + 120, 20, XSetForeground(dpy, gc, theme.spec.selBg);
"Press 0-9 or Left Softkey to dial", 200); XFillRectangle(dpy, pix, gc, tx, 0, tw, kHeaderH - 2);
} else { XSetForeground(dpy, gc, (t == 4) ? theme.spec.danger : theme.spec.accent);
int total = static_cast<int>(filtered.size()); XFillRectangle(dpy, pix, gc, tx, kHeaderH - 3, tw, 3);
int scrollOffset = 0;
if (selected >= kVisibleRows) {
scrollOffset = selected - kVisibleRows + 1;
}
if (scrollOffset > std::max(0, total - kVisibleRows)) {
scrollOffset = std::max(0, total - kVisibleRows);
}
for (int i = 0; i < kVisibleRows; ++i) {
int idx = scrollOffset + i;
if (idx >= total) {
break;
} }
int ry = kListY + (i * kListH) / kVisibleRows; std::string lbl(kTabLabels[t]);
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows; int lw = pangoWidth(layoutSmall, lbl);
int rowH = nextRy - ry; XftColor* tabCol = active ? ((t == 4) ? &colRed : &colWhite) : &colMuted;
bool isSel = (idx == selected); drawPango(layoutSmall, tabCol, tx + (tw - lw) / 2, 0, kHeaderH - 2, lbl);
}
XSetForeground(dpy, gc, XSetForeground(dpy, gc, theme.spec.border);
isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
if (filtered.empty()) {
drawPango(layoutIcon, &colMuted, 112, kListY + 54, 24,
(filterTab == 4) ? kIconShield : kIconPhoneLog);
std::string emptyTitle =
(filterTab == 4) ? "No blocked calls" : "No call history";
int etw = pangoWidth(layoutMain, emptyTitle);
drawPango(layoutMain, &colMuted, std::max(8, (kWinWidth - etw) / 2),
kListY + 84, 26, emptyTitle, 224);
std::string emptySub =
(filterTab == 4)
? ("Blocklist: " + std::to_string(blockList.entries().size()) +
" number(s)")
: "Press 0-9 to dial or Options";
int esw = pangoWidth(layoutSmall, emptySub);
drawPango(layoutSmall, &colMuted, std::max(8, (kWinWidth - esw) / 2),
kListY + 114, 20, emptySub, 224);
} else {
int total = static_cast<int>(filtered.size());
int scrollOffset = 0;
if (selected >= kVisibleRows) {
scrollOffset = selected - kVisibleRows + 1;
}
if (scrollOffset > std::max(0, total - kVisibleRows)) {
scrollOffset = std::max(0, total - kVisibleRows);
}
for (int i = 0; i < kVisibleRows; ++i) {
int idx = scrollOffset + i;
if (idx >= total) {
break;
}
int ry = kListY + (i * kListH) / kVisibleRows;
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
int rowH = nextRy - ry;
bool isSel = (idx == selected);
XSetForeground(
dpy, gc,
isSel ? theme.spec.selBg
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
if (isSel) {
XSetForeground(dpy, gc, theme.spec.accent);
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
}
const CallLogItem& item = filtered[idx];
const char* dirIcon = kIconInCall;
XftColor* dirCol = &colGreen;
const char* dirName = "In";
if (item.direction == CallDirection::Blocked) {
dirIcon = kIconBan;
dirCol = &colRed;
dirName = "Blocked";
} else if (item.direction == CallDirection::Outgoing) {
dirIcon = kIconOutCall;
dirCol = &colBlue;
dirName = "Out";
} else if (item.direction == CallDirection::Missed) {
dirIcon = kIconMissedCall;
dirCol = &colRed;
dirName = "Missed";
}
drawPango(layoutIcon, dirCol, 8, ry, rowH, dirIcon);
std::string rightBadge = formatDuration(item.durationSec);
XftColor* badgeCol = isSel ? &colYellow : &colGreen;
if (item.isBlockedNumber) {
rightBadge = "BLK";
badgeCol = &colRed;
}
int durW = 0;
if (!rightBadge.empty()) {
durW = pangoWidth(layoutSmall, rightBadge);
drawPango(layoutSmall, badgeCol, kWinWidth - durW - 6, ry + 3, 22,
rightBadge);
}
std::string primary =
item.contactName.empty() ? item.number : item.contactName;
XftColor* priCol =
((item.direction == CallDirection::Missed ||
item.direction == CallDirection::Blocked ||
item.isBlockedNumber) &&
!isSel)
? &colRed
: (isSel ? &colWhite : &colLight);
int priMaxW = (durW > 0) ? (kWinWidth - 36 - durW - 6) : 200;
drawPango(layoutMain, priCol, 30, ry + 3, 22, primary, priMaxW);
int timeW = pangoWidth(layoutSmall, item.displayTime);
drawPango(layoutSmall, isSel ? &colWhite : &colMuted,
kWinWidth - timeW - 6, ry + 24, 19, item.displayTime);
std::string secondary =
!item.contactName.empty() ? item.number : std::string(dirName);
int secMaxW = std::max(60, kWinWidth - 36 - timeW - 8);
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24,
19, secondary, secMaxW);
}
if (total > kVisibleRows) {
int sbH = std::max(16, (kListH * kVisibleRows) / total);
int sbY = kListY +
((kListH - sbH) * scrollOffset) / (total - kVisibleRows);
XSetForeground(dpy, gc, theme.spec.divider);
XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH);
}
}
} else if (mode == ViewMode::Options) {
drawPango(layoutIcon, &colYellow, 8, 0, kHeaderH, kIconList);
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, "Call Log Options", 200);
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
constexpr int kOptVis = 6;
int total = static_cast<int>(options.size());
int scrollOffset = 0;
if (optionSelected >= kOptVis) {
scrollOffset = optionSelected - kOptVis + 1;
}
int rowH = kListH / kOptVis;
for (int i = 0; i < kOptVis; ++i) {
int idx = scrollOffset + i;
if (idx >= total) break;
int ry = kListY + i * rowH;
bool isSel = (idx == optionSelected);
XSetForeground(
dpy, gc,
isSel ? theme.spec.selBg
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
if (isSel) { if (isSel) {
XSetForeground(dpy, gc, theme.spec.accent); XSetForeground(dpy, gc,
options[idx].danger ? theme.spec.danger : theme.spec.accent);
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
} }
XftColor* icCol = options[idx].danger
const CallLogItem& item = filtered[idx]; ? &colRed
const char* dirIcon = kIconInCall; : (isSel ? &colYellow : &colBlue);
XftColor* dirCol = &colGreen; XftColor* txCol = options[idx].danger
const char* dirName = "In"; ? &colRed
if (item.direction == CallDirection::Outgoing) { : (isSel ? &colWhite : &colLight);
dirIcon = kIconOutCall; drawPango(layoutIcon, icCol, 10, ry, rowH, options[idx].icon);
dirCol = &colBlue; drawPango(layoutMain, txCol, 34, ry, rowH, options[idx].label,
dirName = "Out"; kWinWidth - 42);
} else if (item.direction == CallDirection::Missed) {
dirIcon = kIconMissedCall;
dirCol = &colRed;
dirName = "Missed";
}
drawPango(layoutIcon, dirCol, 8, ry, rowH, dirIcon);
std::string dur = formatDuration(item.durationSec);
int durW = 0;
if (!dur.empty()) {
durW = pangoWidth(layoutSmall, dur);
drawPango(layoutSmall, isSel ? &colYellow : &colGreen,
kWinWidth - durW - 6, ry + 3, 22, dur);
}
std::string primary = item.contactName.empty() ? item.number : item.contactName;
XftColor* priCol = (item.direction == CallDirection::Missed && !isSel)
? &colRed
: (isSel ? &colWhite : &colLight);
int priMaxW = (durW > 0) ? (kWinWidth - 36 - durW - 6) : 200;
drawPango(layoutMain, priCol, 30, ry + 3, 22, primary, priMaxW);
int timeW = pangoWidth(layoutSmall, item.displayTime);
drawPango(layoutSmall, isSel ? &colWhite : &colMuted,
kWinWidth - timeW - 6, ry + 24, 19, item.displayTime);
std::string secondary =
!item.contactName.empty() ? item.number : std::string(dirName);
int secMaxW = std::max(60, kWinWidth - 36 - timeW - 8);
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24, 19,
secondary, secMaxW);
} }
softLeft = "Select";
softCenter = "OK";
softRight = "Back";
} else if (mode == ViewMode::Blocklist) {
drawPango(layoutIcon, &colRed, 8, 0, kHeaderH, kIconBan);
std::string hdr =
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, hdr, 204);
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
if (total > kVisibleRows) { const auto& entries = blockList.entries();
int sbH = std::max(16, (kListH * kVisibleRows) / total); if (entries.empty()) {
int sbY = kListY + drawPango(layoutIcon, &colMuted, 112, kListY + 54, 24, kIconShield);
((kListH - sbH) * scrollOffset) / (total - kVisibleRows); drawPango(layoutMain, &colMuted, 44, kListY + 84, 26,
XSetForeground(dpy, gc, theme.spec.divider); "Blocklist is empty", 180);
XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH); drawPango(layoutSmall, &colMuted, 22, kListY + 114, 20,
"Left Softkey: Block a Number", 204);
} else {
int total = static_cast<int>(entries.size());
int scrollOffset = 0;
if (blockSelected >= kVisibleRows) {
scrollOffset = blockSelected - kVisibleRows + 1;
}
for (int i = 0; i < kVisibleRows; ++i) {
int idx = scrollOffset + i;
if (idx >= total) break;
int ry = kListY + (i * kListH) / kVisibleRows;
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
int rowH = nextRy - ry;
bool isSel = (idx == blockSelected);
XSetForeground(
dpy, gc,
isSel ? theme.spec.selBg
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
if (isSel) {
XSetForeground(dpy, gc, theme.spec.danger);
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
}
const auto& e = entries[idx];
std::string cname = book.resolveName(e.number);
std::string topLine = cname.empty() ? e.number : cname;
std::string subLine =
cname.empty() ? e.label : (e.number + " (" + e.label + ")");
drawPango(layoutIcon, &colRed, 8, ry, rowH, kIconBan);
drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry + 3, 22,
topLine, kWinWidth - 38);
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24,
19, subLine, kWinWidth - 38);
}
} }
softLeft = "Add New";
softCenter = entries.empty() ? "" : "Unblock";
softRight = "Back";
} else if (mode == ViewMode::AddBlock) {
drawPango(layoutIcon, &colRed, 8, 0, kHeaderH, kIconBan);
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, "Block Phone Number", 204);
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
drawPango(layoutSmall, &colMuted, 12, kListY + 18, 20,
"Enter number or prefix (e.g. +1800*):", kWinWidth - 24);
XSetForeground(dpy, gc, theme.spec.bgAltOdd);
XFillRectangle(dpy, pix, gc, 12, kListY + 44, kWinWidth - 24, 34);
XSetForeground(dpy, gc, theme.spec.accent);
XDrawRectangle(dpy, pix, gc, 12, kListY + 44, kWinWidth - 25, 33);
std::string disp = addBlockInput + "_";
drawPango(layoutMain, &colWhite, 18, kListY + 47, 28, disp, kWinWidth - 36);
drawPango(layoutSmall, &colMuted, 12, kListY + 90, 20,
"0-9 / * / #: Digits #: + prefix", kWinWidth - 24);
drawPango(layoutSmall, &colMuted, 12, kListY + 112, 20,
"Left/Down: Backspace", kWinWidth - 24);
softLeft = "Block";
softCenter = "SAVE";
softRight = "Cancel";
}
if (!toastMsg.empty() && nowMs() < toastUntilMs) {
int tw = std::min(kWinWidth - 16, pangoWidth(layoutSmall, toastMsg) + 20);
int tx = (kWinWidth - tw) / 2;
int ty = kWinHeight - kSoftbarH - 28;
XSetForeground(dpy, gc, theme.spec.selBg);
XFillRectangle(dpy, pix, gc, tx, ty, tw, 24);
XSetForeground(dpy, gc, theme.spec.accent);
XDrawRectangle(dpy, pix, gc, tx, ty, tw - 1, 23);
int textW = pangoWidth(layoutSmall, toastMsg);
drawPango(layoutSmall, &colYellow, tx + (tw - textW) / 2, ty + 2, 20,
toastMsg, tw - 8);
} }
int sbY = kWinHeight - kSoftbarH; int sbY = kWinHeight - kSoftbarH;
@@ -310,15 +558,21 @@ int calllog_run_main(int argc, char** argv) {
XSetForeground(dpy, gc, theme.spec.border); XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1); XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1);
drawPango(layoutMain, &colLight, 6, sbY + 1, kSoftbarH - 1, "Dialer"); drawPango(layoutMain, &colLight, 6, sbY + 1, kSoftbarH - 1, softLeft);
std::string centerLabel = "CALL"; if (!softCenter.empty()) {
int cw = pangoWidth(layoutMain, centerLabel); int cw = pangoWidth(layoutMain, softCenter);
drawPango(layoutMain, filtered.empty() ? &colMuted : &colGreen, XftColor* cCol = &colGreen;
(kWinWidth - cw) / 2, sbY + 1, kSoftbarH - 1, centerLabel); if (mode == ViewMode::Log && filtered.empty()) {
std::string rightLabel = "Close"; cCol = &colMuted;
int rw = pangoWidth(layoutMain, rightLabel); } else if (mode == ViewMode::Blocklist) {
cCol = &colRed;
}
drawPango(layoutMain, cCol, (kWinWidth - cw) / 2, sbY + 1, kSoftbarH - 1,
softCenter);
}
int rw = pangoWidth(layoutMain, softRight);
drawPango(layoutMain, &colLight, kWinWidth - rw - 6, sbY + 1, drawPango(layoutMain, &colLight, kWinWidth - rw - 6, sbY + 1,
kSoftbarH - 1, rightLabel); kSoftbarH - 1, softRight);
if (visible) { if (visible) {
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
@@ -326,9 +580,124 @@ int calllog_run_main(int argc, char** argv) {
} }
}; };
auto executeOption = [&](int optId) {
bool hasSel = (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size()));
std::string selNum = hasSel ? filtered[selected].number : "";
std::string selName = hasSel ? filtered[selected].contactName : "";
switch (optId) {
case 1: { // Call Number
if (!selNum.empty()) {
mode = ViewMode::Log;
writeSoftkeySuppress(nowMs() + 350);
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
placePhoneCall(selNum);
}
break;
}
case 2: { // Open in Dialer
mode = ViewMode::Log;
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
if (!sendPluginCommand("dialer",
selNum.empty() ? "OPEN" : ("OPEN " + selNum))) {
if (selNum.empty()) {
runAsyncArgv("xdialer");
} else {
runAsyncArgv("xdialer", selNum.c_str());
}
}
break;
}
case 3: { // Send SMS
if (!selNum.empty()) {
mode = ViewMode::Log;
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
if (!sendPluginCommand("sms", "OPEN " + selNum)) {
runAsyncArgv("xsms", selNum.c_str());
}
}
break;
}
case 4: { // Block / Unblock Number
if (!selNum.empty()) {
if (blockList.isBlocked(selNum)) {
blockList.unblockNumber(selNum);
showToast("Unblocked " + selNum);
} else {
blockList.blockNumber(selNum, selName.empty() ? "Call Log" : selName);
showToast("Blocked " + selNum);
}
reloadLogsIfNeeded(true);
}
mode = ViewMode::Log;
render();
break;
}
case 5: { // Contacts
mode = ViewMode::Log;
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
if (!sendPluginCommand("contacts", "OPEN")) {
runAsyncArgv("xcontacts");
}
break;
}
case 6: { // Manage Blocklist
blockList.load();
blockSelected = 0;
mode = ViewMode::Blocklist;
render();
break;
}
case 7: { // Block a Number...
addBlockInput = selNum;
mode = ViewMode::AddBlock;
render();
break;
}
case 8: { // Clear Spam Log
clearCallLogFile(getBlockedModemLogFilePath());
reloadLogsIfNeeded(true);
showToast("Cleared blocked call log");
mode = ViewMode::Log;
render();
break;
}
case 9: { // Clear Call Log
clearCallLogFile(getModemLogFilePath());
reloadLogsIfNeeded(true);
showToast("Cleared call history");
mode = ViewMode::Log;
render();
break;
}
default:
mode = ViewMode::Log;
render();
break;
}
};
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup // Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
drawPango(layoutIcon, &colGreen, 0, 0, 20, drawPango(layoutIcon, &colGreen, 0, 0, 20,
std::string(kIconInCall) + kIconOutCall + kIconMissedCall + kIconPhoneLog); std::string(kIconInCall) + kIconOutCall + kIconMissedCall +
kIconPhoneLog + kIconBan + kIconShield);
render(); render();
XSync(dpy, False); XSync(dpy, False);
@@ -344,6 +713,7 @@ int calllog_run_main(int argc, char** argv) {
initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0); initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
softkeyPending = 0; softkeyPending = 0;
softkeyDeadlineMs = 0; softkeyDeadlineMs = 0;
mode = ViewMode::Log;
visible = true; visible = true;
XMapRaised(dpy, win); XMapRaised(dpy, win);
if (!themeChanged) { if (!themeChanged) {
@@ -392,7 +762,8 @@ int calllog_run_main(int argc, char** argv) {
visible = false; visible = false;
softkeyPending = 0; softkeyPending = 0;
softkeyDeadlineMs = 0; softkeyDeadlineMs = 0;
if (filterTab != 0 || selected != 0) { if (mode != ViewMode::Log || filterTab != 0 || selected != 0) {
mode = ViewMode::Log;
filterTab = 0; filterTab = 0;
selected = 0; selected = 0;
applyFilter(); applyFilter();
@@ -422,6 +793,11 @@ int calllog_run_main(int argc, char** argv) {
} }
} }
if (visible && !toastMsg.empty() && nowMs() >= toastUntilMs) {
toastMsg.clear();
render();
}
if (visible && !initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) { if (visible && !initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
initialSoftkeysReleased = true; initialSoftkeysReleased = true;
} }
@@ -485,6 +861,128 @@ int calllog_run_main(int argc, char** argv) {
continue; continue;
} }
if (mode == ViewMode::AddBlock) {
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
if (dialCh == '#') {
if (addBlockInput.empty()) {
addBlockInput.push_back('+');
} else {
addBlockInput.push_back('#');
}
render();
continue;
} else if (dialCh != '\0') {
addBlockInput.push_back(dialCh);
render();
continue;
}
if (sym == XK_BackSpace || sym == XK_Left || sym == XK_Down) {
if (!addBlockInput.empty()) {
addBlockInput.pop_back();
render();
}
continue;
} else if (sym == XK_Escape) {
mode = ViewMode::Blocklist;
render();
continue;
} else if (sym == XK_Return || sym == XK_KP_Enter) {
std::string num = trimStr(addBlockInput);
if (!normalizePhone(num).empty()) {
blockList.blockNumber(num, "Manual");
reloadLogsIfNeeded(true);
showToast("Blocked " + num);
}
mode = ViewMode::Blocklist;
render();
continue;
}
continue;
}
if (mode == ViewMode::Options) {
switch (sym) {
case XK_Escape:
case XK_BackSpace:
case XK_Left:
mode = ViewMode::Log;
render();
break;
case XK_Up:
case XK_KP_Up:
case XK_k:
if (!options.empty()) {
optionSelected =
(optionSelected + static_cast<int>(options.size()) - 1) %
static_cast<int>(options.size());
render();
}
break;
case XK_Down:
case XK_KP_Down:
case XK_j:
if (!options.empty()) {
optionSelected =
(optionSelected + 1) % static_cast<int>(options.size());
render();
}
break;
case XK_Return:
case XK_KP_Enter:
if (!options.empty() && optionSelected >= 0 &&
optionSelected < static_cast<int>(options.size())) {
executeOption(options[optionSelected].id);
}
break;
default:
break;
}
continue;
}
if (mode == ViewMode::Blocklist) {
switch (sym) {
case XK_Escape:
case XK_BackSpace:
case XK_Left:
mode = ViewMode::Log;
render();
break;
case XK_Up:
case XK_KP_Up:
case XK_k:
if (!blockList.entries().empty()) {
int n = static_cast<int>(blockList.entries().size());
blockSelected = (blockSelected + n - 1) % n;
render();
}
break;
case XK_Down:
case XK_KP_Down:
case XK_j:
if (!blockList.entries().empty()) {
int n = static_cast<int>(blockList.entries().size());
blockSelected = (blockSelected + 1) % n;
render();
}
break;
case XK_Return:
case XK_KP_Enter:
if (!blockList.entries().empty() && blockSelected >= 0 &&
blockSelected < static_cast<int>(blockList.entries().size())) {
std::string num = blockList.entries()[blockSelected].number;
blockList.unblockNumber(num);
reloadLogsIfNeeded(true);
showToast("Unblocked " + num);
render();
}
break;
default:
break;
}
continue;
}
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state); char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
if (dialCh != '\0') { if (dialCh != '\0') {
std::string arg(1, dialCh); std::string arg(1, dialCh);
@@ -505,7 +1003,7 @@ int calllog_run_main(int argc, char** argv) {
case XK_Left: case XK_Left:
case XK_KP_Left: case XK_KP_Left:
case XK_h: case XK_h:
filterTab = (filterTab + 3) % 4; filterTab = (filterTab + kNumTabs - 1) % kNumTabs;
selected = 0; selected = 0;
applyFilter(); applyFilter();
render(); render();
@@ -513,7 +1011,7 @@ int calllog_run_main(int argc, char** argv) {
case XK_Right: case XK_Right:
case XK_KP_Right: case XK_KP_Right:
case XK_l: case XK_l:
filterTab = (filterTab + 1) % 4; filterTab = (filterTab + 1) % kNumTabs;
selected = 0; selected = 0;
applyFilter(); applyFilter();
render(); render();
@@ -549,9 +1047,16 @@ int calllog_run_main(int argc, char** argv) {
hideWindow(true); hideWindow(true);
placePhoneCall(num); placePhoneCall(num);
} else if (filtered.empty()) { } else if (filtered.empty()) {
hideWindow(false); if (filterTab == 4) {
if (!sendPluginCommand("dialer", "OPEN")) { blockList.load();
runAsyncArgv("xdialer"); blockSelected = 0;
mode = ViewMode::Blocklist;
render();
} else {
hideWindow(false);
if (!sendPluginCommand("dialer", "OPEN")) {
runAsyncArgv("xdialer");
}
} }
} }
break; break;
@@ -572,22 +1077,40 @@ int calllog_run_main(int argc, char** argv) {
runAsyncArgv("xdrawer"); runAsyncArgv("xdrawer");
} }
} else if (comb == 1) { } else if (comb == 1) {
std::string arg; if (mode == ViewMode::Log) {
if (!filtered.empty() && selected >= 0 && buildOptions();
selected < static_cast<int>(filtered.size())) { mode = ViewMode::Options;
arg = filtered[selected].number; render();
} } else if (mode == ViewMode::Options) {
hideWindow(false); if (!options.empty() && optionSelected >= 0 &&
if (!sendPluginCommand("dialer", arg.empty() ? "OPEN" : ("OPEN " + arg))) { optionSelected < static_cast<int>(options.size())) {
if (arg.empty()) { executeOption(options[optionSelected].id);
runAsyncArgv("xdialer");
} else {
runAsyncArgv("xdialer", arg.c_str());
} }
} else if (mode == ViewMode::Blocklist) {
addBlockInput.clear();
mode = ViewMode::AddBlock;
render();
} else if (mode == ViewMode::AddBlock) {
std::string num = trimStr(addBlockInput);
if (!normalizePhone(num).empty()) {
blockList.blockNumber(num, "Manual");
reloadLogsIfNeeded(true);
showToast("Blocked " + num);
}
mode = ViewMode::Blocklist;
render();
} }
} else if (comb == 2) { } else if (comb == 2) {
writeSoftkeySuppress(nowMs() + 350); if (mode == ViewMode::AddBlock) {
hideWindow(true); mode = ViewMode::Blocklist;
render();
} else if (mode == ViewMode::Options || mode == ViewMode::Blocklist) {
mode = ViewMode::Log;
render();
} else {
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
}
} }
} }
@@ -653,46 +1176,24 @@ int calllog_on_global_key(const DwmHostApi* host, KeySym keysym,
} }
return 1; return 1;
} }
} if (!sendPluginCommand("calllog", "OPEN")) {
return 0; runAsyncArgv("xcalllog");
}
int calllog_on_idle_key(const DwmHostApi* host, KeySym keysym,
unsigned int keycode, unsigned int state) {
if (!host || !host->is_idle_desktop || !host->is_idle_desktop() || state != 0) {
return 0;
}
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
if (isCallRingingFast()) {
if (!sendPluginCommand("callscreen", "ACCEPT")) {
runAsyncArgv("xcallscreen", "accept");
}
return 1;
}
if (isCallActiveOrRingingFast()) {
if (!sendPluginCommand("callscreen", "OPEN")) {
runAsyncArgv("xcallscreen");
}
return 1;
}
if (host->spawn_plugin) {
host->spawn_plugin("calllog", nullptr);
} }
return 1; return 1;
} }
return 0; return 0;
} }
} // namespace } // namespace
extern "C" __attribute__((visibility("default"))) const DwmPlugin dwm_plugin_entry = { extern "C" __attribute__((visibility("default"))) const DwmPlugin dwm_plugin_entry = {
DWM_PLUGIN_API_VERSION, DWM_PLUGIN_API_VERSION,
"calllog", "calllog",
"Keypad-driven call log viewer & dialer launcher (opens on idle Call key)", "Preloaded resident X11 call log viewer, anti-spam blocklist & dialer launcher",
calllog_plugin_init, calllog_plugin_init,
nullptr, nullptr,
nullptr, nullptr,
calllog_on_idle_key, nullptr,
calllog_on_global_key, calllog_on_global_key,
calllog_run_main calllog_run_main
}; };
+13
View File
@@ -118,6 +118,10 @@ bool isMediaExt(const std::string& ext) {
ext == "avi"; ext == "avi";
} }
bool isGameBoyExt(const std::string& ext) {
return ext == "gb" || ext == "gbc";
}
bool isLikelyTextFile(const std::string& path, off_t size) { bool isLikelyTextFile(const std::string& path, off_t size) {
if (size == 0) return true; if (size == 0) return true;
if (size > 256 * 1024) return false; if (size > 256 * 1024) return false;
@@ -612,6 +616,8 @@ int runFilesApp(int argc, char** argv) {
} else if (isAudioExt(ext)) { } else if (isAudioExt(ext)) {
actionsList.push_back({1, kIconMusic, "Play in Music Player"}); actionsList.push_back({1, kIconMusic, "Play in Music Player"});
actionsList.push_back({15, kIconBell, "Set as Ringtone"}); actionsList.push_back({15, kIconBell, "Set as Ringtone"});
} else if (isGameBoyExt(ext)) {
actionsList.push_back({1, kIconGamepad, "Play in Game Boy Emulator"});
} else { } else {
actionsList.push_back({1, kIconEye, "Open / View"}); actionsList.push_back({1, kIconEye, "Open / View"});
} }
@@ -689,6 +695,10 @@ int runFilesApp(int argc, char** argv) {
} else if (isAudioExt(ext)) { } else if (isAudioExt(ext)) {
openInMusicPlayer(item.fullPath); openInMusicPlayer(item.fullPath);
return; return;
} else if (isGameBoyExt(ext)) {
hideWindow();
runAsyncArgv("gambatte", item.fullPath.c_str());
return;
} else if (isMediaExt(ext)) { } else if (isMediaExt(ext)) {
hideWindow(); hideWindow();
runAsyncArgv("sxmo_open.sh", item.fullPath.c_str()); runAsyncArgv("sxmo_open.sh", item.fullPath.c_str());
@@ -832,6 +842,9 @@ int runFilesApp(int argc, char** argv) {
} else if (isMediaExt(ext)) { } else if (isMediaExt(ext)) {
ic = kIconMusic; ic = kIconMusic;
icCol = &colBlue; icCol = &colBlue;
} else if (isGameBoyExt(ext)) {
ic = kIconGamepad;
icCol = &colYellow;
} else if (it.isExec || ext == "sh") { } else if (it.isExec || ext == "sh") {
ic = kIconTerm; ic = kIconTerm;
icCol = &colOrange; icCol = &colOrange;
+351 -17
View File
@@ -93,6 +93,9 @@ constexpr const char* kIconToggleOff = "\xef\x88\x84"; // U+F204 toggle off
constexpr const char* kIconKey = "\xef\x82\x84"; // U+F084 key / PIN constexpr const char* kIconKey = "\xef\x82\x84"; // U+F084 key / PIN
constexpr const char* kIconSim = "\xf3\xb0\x99\x95"; // U+F0655 nf-md-sim constexpr const char* kIconSim = "\xf3\xb0\x99\x95"; // U+F0655 nf-md-sim
constexpr const char* kIconBell = "\xef\x83\xb3"; // U+F0F3 bell / ringtone constexpr const char* kIconBell = "\xef\x83\xb3"; // U+F0F3 bell / ringtone
constexpr const char* kIconBan = "\xef\x81\x9e"; // U+F05E ban / block
constexpr const char* kIconShield = "\xef\x84\xb2"; // U+F132 shield / anti-spam
constexpr const char* kIconGamepad = "\xef\x84\x9b"; // U+F11B gamepad / emulator
inline int64_t nowMs() { inline int64_t nowMs() {
struct timespec ts {}; struct timespec ts {};
@@ -947,10 +950,247 @@ class ContactBook {
std::unordered_map<std::string, std::string> suffixMap_; std::unordered_map<std::string, std::string> suffixMap_;
}; };
inline std::string getBlockFilePath() {
const char* env = std::getenv("SXMO_BLOCKFILE");
if (env && env[0]) {
return env;
}
const char* xdg = std::getenv("XDG_CONFIG_HOME");
if (xdg && xdg[0]) {
return std::string(xdg) + "/sxmo/block.tsv";
}
const char* home = std::getenv("HOME");
return std::string(home && home[0] ? home : "/home/user") +
"/.config/sxmo/block.tsv";
}
inline std::string getBlockDirPath() {
const char* env = std::getenv("SXMO_BLOCKDIR");
if (env && env[0]) {
return env;
}
const char* xdg = std::getenv("XDG_DATA_HOME");
if (xdg && xdg[0]) {
return std::string(xdg) + "/sxmo/block";
}
const char* home = std::getenv("HOME");
return std::string(home && home[0] ? home : "/home/user") +
"/.local/share/sxmo/block";
}
inline std::string getBlockedModemLogFilePath() {
return getBlockDirPath() + "/modemlog.tsv";
}
struct BlockEntry {
std::string number;
std::string normNumber;
std::string label;
};
class BlockList {
public:
void load() {
entries_.clear();
exactSet_.clear();
suffixSet_.clear();
std::ifstream bf(getBlockFilePath());
std::string line;
while (std::getline(bf, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (line.empty()) {
continue;
}
size_t tab = line.find('\t');
std::string num = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
std::string lbl = tab == std::string::npos ? "" : trimStr(line.substr(tab + 1));
std::string norm = normalizePhone(num);
if (norm.empty()) {
continue;
}
BlockEntry e;
e.number = num;
e.normNumber = norm;
e.label = lbl.empty() ? "Blocked" : lbl;
entries_.push_back(e);
exactSet_.insert(norm);
std::string sfx = phoneSuffixKey(norm);
if (sfx.size() >= 7) {
suffixSet_.insert(sfx);
}
}
}
bool isBlocked(const std::string& rawNumber) const {
std::string norm = normalizePhone(rawNumber);
if (norm.empty()) {
return false;
}
if (exactSet_.count(norm) > 0) {
return true;
}
std::string sfx = phoneSuffixKey(norm);
if (sfx.size() >= 7 && suffixSet_.count(sfx) > 0) {
return true;
}
for (const auto& e : entries_) {
if (!e.normNumber.empty() && e.normNumber.back() == '*' &&
e.normNumber.size() > 1) {
std::string prefix = e.normNumber.substr(0, e.normNumber.size() - 1);
if (norm.rfind(prefix, 0) == 0) {
return true;
}
}
}
return false;
}
bool blockNumber(const std::string& rawNumber,
const std::string& labelRaw = "") {
std::string num = trimStr(rawNumber);
std::string norm = normalizePhone(num);
if (norm.empty()) {
return false;
}
if (num.find('\t') != std::string::npos ||
num.find('\n') != std::string::npos) {
num = norm;
}
std::string label = trimStr(labelRaw);
for (char& ch : label) {
if (ch == '\t' || ch == '\r' || ch == '\n') {
ch = ' ';
}
}
if (label.empty()) {
label = "Blocked";
}
std::string path = getBlockFilePath();
ContactBook::ensureParentDir(path);
std::vector<std::pair<std::string, std::string>> rows;
{
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (line.empty()) continue;
size_t tab = line.find('\t');
std::string rNum = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
std::string rLbl = tab == std::string::npos ? "Blocked" : trimStr(line.substr(tab + 1));
if (!normalizePhone(rNum).empty()) {
rows.push_back({rNum, rLbl.empty() ? "Blocked" : rLbl});
}
}
}
std::string sfx = phoneSuffixKey(norm);
bool updated = false;
for (auto& r : rows) {
std::string rNorm = normalizePhone(r.first);
std::string rSfx = phoneSuffixKey(rNorm);
if (rNorm == norm || (sfx.size() >= 7 && rSfx == sfx)) {
r.first = num;
if (!labelRaw.empty()) {
r.second = label;
}
updated = true;
break;
}
}
if (!updated) {
rows.push_back({num, label});
}
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
for (const auto& r : rows) {
out << r.first << "\t" << r.second << "\n";
}
out.close();
load();
return true;
}
bool unblockNumber(const std::string& rawNumber) {
std::string norm = normalizePhone(rawNumber);
if (norm.empty()) {
return false;
}
std::string sfx = phoneSuffixKey(norm);
std::string path = getBlockFilePath();
std::vector<std::pair<std::string, std::string>> rows;
bool removed = false;
{
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (line.empty()) continue;
size_t tab = line.find('\t');
std::string rNum = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
std::string rLbl = tab == std::string::npos ? "Blocked" : trimStr(line.substr(tab + 1));
std::string rNorm = normalizePhone(rNum);
if (rNorm.empty()) continue;
std::string rSfx = phoneSuffixKey(rNorm);
if (rNorm == norm || (sfx.size() >= 7 && rSfx == sfx)) {
removed = true;
continue;
}
rows.push_back({rNum, rLbl.empty() ? "Blocked" : rLbl});
}
}
if (!removed) {
return false;
}
ContactBook::ensureParentDir(path);
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
for (const auto& r : rows) {
out << r.first << "\t" << r.second << "\n";
}
out.close();
load();
return true;
}
bool clearAll() {
std::string path = getBlockFilePath();
ContactBook::ensureParentDir(path);
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
out.close();
load();
return true;
}
const std::vector<BlockEntry>& entries() const { return entries_; }
private:
std::vector<BlockEntry> entries_;
std::unordered_set<std::string> exactSet_;
std::unordered_set<std::string> suffixSet_;
};
enum class CallDirection { enum class CallDirection {
Incoming = 0, Incoming = 0,
Outgoing = 1, Outgoing = 1,
Missed = 2, Missed = 2,
Blocked = 3,
}; };
struct CallLogItem { struct CallLogItem {
@@ -959,6 +1199,7 @@ struct CallLogItem {
std::string contactName; std::string contactName;
std::string displayTime; std::string displayTime;
int durationSec = 0; int durationSec = 0;
bool isBlockedNumber = false;
}; };
inline time_t parseIsoTime(const std::string& iso) { inline time_t parseIsoTime(const std::string& iso) {
@@ -999,8 +1240,10 @@ inline std::string formatDuration(int sec) {
return buf; return buf;
} }
inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) { inline std::vector<CallLogItem> loadCallLogFromFile(const ContactBook& book,
std::ifstream lf(getModemLogFilePath()); const std::string& logPath,
bool forceBlockedDir = false) {
std::ifstream lf(logPath);
std::string line; std::string line;
struct ActiveSession { struct ActiveSession {
@@ -1022,7 +1265,7 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
return; return;
} }
CallLogItem item; CallLogItem item;
item.direction = cur.dir; item.direction = forceBlockedDir ? CallDirection::Blocked : cur.dir;
item.number = cur.number; item.number = cur.number;
item.contactName = book.resolveName(cur.number); item.contactName = book.resolveName(cur.number);
item.displayTime = formatShortTime(cur.startTimeStr); item.displayTime = formatShortTime(cur.startTimeStr);
@@ -1053,10 +1296,24 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
time_t epoch = parseIsoTime(ts); time_t epoch = parseIsoTime(ts);
if (evt == "call_ring") { if (evt == "call_blocked") {
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
cur.dir = CallDirection::Blocked;
flushSession();
} else {
flushSession();
CallLogItem item;
item.direction = CallDirection::Blocked;
item.number = num;
item.contactName = book.resolveName(num);
item.displayTime = formatShortTime(ts);
item.durationSec = 0;
history.push_back(item);
}
} else if (evt == "call_ring") {
flushSession(); flushSession();
cur.active = true; cur.active = true;
cur.dir = CallDirection::Missed; cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
cur.number = num; cur.number = num;
cur.startTimeStr = ts; cur.startTimeStr = ts;
cur.startEpoch = epoch; cur.startEpoch = epoch;
@@ -1088,12 +1345,12 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
} }
} else if (evt == "call_missed") { } else if (evt == "call_missed") {
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) { if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
cur.dir = CallDirection::Missed; cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
flushSession(); flushSession();
} else { } else {
flushSession(); flushSession();
CallLogItem item; CallLogItem item;
item.direction = CallDirection::Missed; item.direction = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
item.number = num; item.number = num;
item.contactName = book.resolveName(num); item.contactName = book.resolveName(num);
item.displayTime = formatShortTime(ts); item.displayTime = formatShortTime(ts);
@@ -1105,10 +1362,10 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) { if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
if (evt == "call_discard" && !cur.answered && if (evt == "call_discard" && !cur.answered &&
cur.dir != CallDirection::Outgoing) { cur.dir != CallDirection::Outgoing) {
cur.dir = CallDirection::Missed; cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
} }
CallLogItem item; CallLogItem item;
item.direction = cur.dir; item.direction = forceBlockedDir ? CallDirection::Blocked : cur.dir;
item.number = cur.number; item.number = cur.number;
item.contactName = book.resolveName(cur.number); item.contactName = book.resolveName(cur.number);
item.displayTime = formatShortTime(cur.startTimeStr); item.displayTime = formatShortTime(cur.startTimeStr);
@@ -1126,6 +1383,44 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
return history; return history;
} }
inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
return loadCallLogFromFile(book, getModemLogFilePath(), false);
}
inline std::vector<CallLogItem> loadBlockedCallLog(const ContactBook& book) {
return loadCallLogFromFile(book, getBlockedModemLogFilePath(), true);
}
inline bool clearCallLogFile(const std::string& logPath) {
std::ifstream in(logPath);
if (!in.is_open()) {
return true;
}
std::vector<std::string> keep;
std::string line;
while (std::getline(in, line)) {
if (line.empty()) continue;
size_t t1 = line.find('\t');
if (t1 == std::string::npos) continue;
size_t t2 = line.find('\t', t1 + 1);
if (t2 == std::string::npos) continue;
std::string evt = trimStr(line.substr(t1 + 1, t2 - t1 - 1));
if (evt.rfind("call_", 0) == 0 || evt == "ring_muted") {
continue;
}
keep.push_back(line);
}
in.close();
std::ofstream out(logPath, std::ios::trunc);
if (!out.is_open()) {
return false;
}
for (const auto& l : keep) {
out << l << "\n";
}
return true;
}
inline void placePhoneCall(const std::string& rawNumber) { inline void placePhoneCall(const std::string& rawNumber) {
std::string num = normalizePhone(rawNumber); std::string num = normalizePhone(rawNumber);
if (num.empty()) { if (num.empty()) {
@@ -1285,6 +1580,7 @@ struct SmsThread {
time_t lastEpoch = 0; time_t lastEpoch = 0;
bool lastIncoming = true; bool lastIncoming = true;
bool hasDraft = false; bool hasDraft = false;
bool isBlocked = false;
int messageCount = 0; int messageCount = 0;
}; };
@@ -1319,9 +1615,12 @@ inline bool parseSmsHeaderLine(const std::string& line,
return false; return false;
} }
inline std::vector<SmsMessage> loadSmsThreadMessages(const std::string& number) { inline std::vector<SmsMessage> loadSmsThreadMessages(
const std::string& number,
const std::string& customBaseDir = "") {
std::vector<SmsMessage> msgs; std::vector<SmsMessage> msgs;
std::string path = getModemLogDirPath() + "/" + number + "/sms.txt"; std::string baseDir = customBaseDir.empty() ? getModemLogDirPath() : customBaseDir;
std::string path = baseDir + "/" + number + "/sms.txt";
std::ifstream in(path); std::ifstream in(path);
if (!in.is_open()) { if (!in.is_open()) {
return msgs; return msgs;
@@ -1424,11 +1723,13 @@ inline void removeSmsDraft(const std::string& number) {
::unlink(path.c_str()); ::unlink(path.c_str());
} }
inline bool deleteSmsThread(const std::string& number) { inline bool deleteSmsThread(const std::string& number,
const std::string& customBaseDir = "") {
if (number.empty()) { if (number.empty()) {
return false; return false;
} }
std::string dir = getModemLogDirPath() + "/" + number; std::string baseDir = customBaseDir.empty() ? getModemLogDirPath() : customBaseDir;
std::string dir = baseDir + "/" + number;
std::string smsPath = dir + "/sms.txt"; std::string smsPath = dir + "/sms.txt";
std::string draftPath = dir + "/draft.txt"; std::string draftPath = dir + "/draft.txt";
::unlink(smsPath.c_str()); ::unlink(smsPath.c_str());
@@ -1437,9 +1738,42 @@ inline bool deleteSmsThread(const std::string& number) {
return true; return true;
} }
inline std::vector<SmsThread> loadSmsThreads(const ContactBook& book) { inline bool moveSmsThreadBetweenDirs(const std::string& number,
const std::string& fromBaseDir,
const std::string& toBaseDir) {
if (number.empty()) {
return false;
}
std::string srcPath = fromBaseDir + "/" + number + "/sms.txt";
std::ifstream in(srcPath);
if (!in.is_open()) {
return false;
}
std::ostringstream ss;
ss << in.rdbuf();
in.close();
std::string content = ss.str();
if (content.empty()) {
return false;
}
std::string dstPath = toBaseDir + "/" + number + "/sms.txt";
ContactBook::ensureParentDir(dstPath);
std::ofstream out(dstPath, std::ios::app);
if (!out.is_open()) {
return false;
}
out << content;
out.close();
deleteSmsThread(number, fromBaseDir);
return true;
}
inline std::vector<SmsThread> loadSmsThreads(
const ContactBook& book,
const std::string& customBaseDir = "") {
std::vector<SmsThread> threads; std::vector<SmsThread> threads;
std::string baseDir = getModemLogDirPath(); bool isSpamDir = !customBaseDir.empty();
std::string baseDir = isSpamDir ? customBaseDir : getModemLogDirPath();
DIR* d = ::opendir(baseDir.c_str()); DIR* d = ::opendir(baseDir.c_str());
if (!d) { if (!d) {
return threads; return threads;
@@ -1459,13 +1793,13 @@ inline std::vector<SmsThread> loadSmsThreads(const ContactBook& book) {
std::string smsPath = subDir + "/sms.txt"; std::string smsPath = subDir + "/sms.txt";
std::string draftPath = subDir + "/draft.txt"; std::string draftPath = subDir + "/draft.txt";
bool hasSms = (::access(smsPath.c_str(), R_OK) == 0); bool hasSms = (::access(smsPath.c_str(), R_OK) == 0);
std::string draft = loadSmsDraft(name); std::string draft = isSpamDir ? "" : loadSmsDraft(name);
bool hasDraft = !draft.empty(); bool hasDraft = !draft.empty();
if (!hasSms && !hasDraft) { if (!hasSms && !hasDraft) {
continue; continue;
} }
auto msgs = loadSmsThreadMessages(name); auto msgs = loadSmsThreadMessages(name, baseDir);
if (msgs.empty() && !hasDraft) { if (msgs.empty() && !hasDraft) {
continue; continue;
} }
+315 -36
View File
@@ -28,6 +28,8 @@ enum class ViewMode {
PickContact = 3, PickContact = 3,
Options = 4, Options = 4,
ConfirmDelete = 5, ConfirmDelete = 5,
Blocklist = 6,
AddBlock = 7,
}; };
enum class TextCaseMode { enum class TextCaseMode {
@@ -171,6 +173,9 @@ int runSmsApp(int argc, char** argv) {
auto& colRed = pt.colRed; auto& colRed = pt.colRed;
ContactBook book; ContactBook book;
BlockList blockList;
std::vector<SmsThread> inboxThreads;
std::vector<SmsThread> spamThreads;
std::vector<SmsThread> allThreads; std::vector<SmsThread> allThreads;
std::vector<SmsThread> filteredThreads; std::vector<SmsThread> filteredThreads;
std::vector<SmsMessage> threadMessages; std::vector<SmsMessage> threadMessages;
@@ -178,7 +183,10 @@ int runSmsApp(int argc, char** argv) {
FileStamp contactsStamp; FileStamp contactsStamp;
FileStamp modemLogStamp; FileStamp modemLogStamp;
FileStamp blockFileStamp;
FileStamp blockLogStamp;
bool loadedOnce = false; bool loadedOnce = false;
bool viewingSpamFolder = false;
ViewMode mode = ViewMode::Threads; ViewMode mode = ViewMode::Threads;
ViewMode prevMode = ViewMode::Threads; ViewMode prevMode = ViewMode::Threads;
@@ -188,6 +196,8 @@ int runSmsApp(int argc, char** argv) {
int selectedOption = 0; int selectedOption = 0;
int selectedPicker = 0; int selectedPicker = 0;
int confirmSelected = 0; int confirmSelected = 0;
int blockSelected = 0;
std::string addBlockInput;
std::string activeNumber; std::string activeNumber;
std::string composeRecipient; std::string composeRecipient;
@@ -216,10 +226,15 @@ int runSmsApp(int argc, char** argv) {
int id; int id;
std::string icon; std::string icon;
std::string label; std::string label;
bool danger = false;
}; };
std::vector<OptionEntry> optionsList; std::vector<OptionEntry> optionsList;
auto updateFilter = [&]() { auto updateFilter = [&]() {
allThreads = viewingSpamFolder ? spamThreads : inboxThreads;
for (auto& t : allThreads) {
t.isBlocked = viewingSpamFolder || blockList.isBlocked(t.number);
}
filteredThreads.clear(); filteredThreads.clear();
if (searchQuery.empty()) { if (searchQuery.empty()) {
filteredThreads = allThreads; filteredThreads = allThreads;
@@ -247,13 +262,22 @@ int runSmsApp(int argc, char** argv) {
auto reloadAllData = [&](bool force = false) { auto reloadAllData = [&](bool force = false) {
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath()); bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
bool lChanged = modemLogStamp.checkAndUpdate(getModemLogFilePath()); bool lChanged = modemLogStamp.checkAndUpdate(getModemLogFilePath());
if (force || cChanged || lChanged || !loadedOnce) { bool bChanged = blockFileStamp.checkAndUpdate(getBlockFilePath());
bool blChanged = blockLogStamp.checkAndUpdate(getBlockedModemLogFilePath());
if (force || cChanged || lChanged || bChanged || blChanged || !loadedOnce) {
loadedOnce = true; loadedOnce = true;
book.load(); book.load();
allThreads = loadSmsThreads(book); blockList.load();
inboxThreads = loadSmsThreads(book, getModemLogDirPath());
spamThreads = loadSmsThreads(book, getBlockDirPath());
updateFilter(); updateFilter();
if (blockSelected >= static_cast<int>(blockList.entries().size())) {
blockSelected = std::max(0, static_cast<int>(blockList.entries().size()) - 1);
}
if (!activeNumber.empty()) { if (!activeNumber.empty()) {
threadMessages = loadSmsThreadMessages(activeNumber); std::string curBase =
viewingSpamFolder ? getBlockDirPath() : getModemLogDirPath();
threadMessages = loadSmsThreadMessages(activeNumber, curBase);
if (selectedMsg >= static_cast<int>(threadMessages.size())) { if (selectedMsg >= static_cast<int>(threadMessages.size())) {
selectedMsg = selectedMsg =
std::max(0, static_cast<int>(threadMessages.size()) - 1); std::max(0, static_cast<int>(threadMessages.size()) - 1);
@@ -266,7 +290,9 @@ int runSmsApp(int argc, char** argv) {
auto openThreadView = [&](const std::string& num) { auto openThreadView = [&](const std::string& num) {
activeNumber = trimStr(num); activeNumber = trimStr(num);
threadMessages = loadSmsThreadMessages(activeNumber); std::string curBase =
viewingSpamFolder ? getBlockDirPath() : getModemLogDirPath();
threadMessages = loadSmsThreadMessages(activeNumber, curBase);
selectedMsg = std::max(0, static_cast<int>(threadMessages.size()) - 1); selectedMsg = std::max(0, static_cast<int>(threadMessages.size()) - 1);
mode = ViewMode::ThreadView; mode = ViewMode::ThreadView;
}; };
@@ -289,22 +315,55 @@ int runSmsApp(int argc, char** argv) {
auto buildOptions = [&]() { auto buildOptions = [&]() {
optionsList.clear(); optionsList.clear();
if (mode == ViewMode::Threads) { if (mode == ViewMode::Threads) {
optionsList.push_back({1, kIconPlus, "New Message"}); optionsList.push_back({1, kIconPlus, "New Message", false});
if (!filteredThreads.empty()) { if (!filteredThreads.empty()) {
optionsList.push_back({2, kIconEye, "Open Conversation"}); const auto& selTh = filteredThreads[selectedThread];
optionsList.push_back({3, kIconSend, "Reply to Selected"}); bool isBlk = blockList.isBlocked(selTh.number);
optionsList.push_back({4, kIconPhone, "Call Contact"}); optionsList.push_back({2, kIconEye, "Open Conversation", false});
optionsList.push_back({5, kIconUser, "Open in Contacts"}); optionsList.push_back({3, kIconSend, "Reply to Selected", false});
optionsList.push_back({6, kIconTrash, "Delete Conversation"}); optionsList.push_back({4, kIconPhone, "Call Contact", false});
if (isBlk || viewingSpamFolder) {
optionsList.push_back({8, kIconCheck, "Unblock & Restore", false});
} else {
optionsList.push_back({8, kIconBan, "Block Number (Spam)", true});
}
optionsList.push_back({5, kIconUser, "Open in Contacts", false});
optionsList.push_back({6, kIconTrash, "Delete Conversation", true});
} }
if (viewingSpamFolder) {
optionsList.push_back(
{9, kIconSms,
"Switch to Inbox (" + std::to_string(inboxThreads.size()) + ")",
false});
} else {
optionsList.push_back(
{9, kIconShield,
"Spam Folder (" + std::to_string(spamThreads.size()) + ")",
false});
}
optionsList.push_back(
{10, kIconBan,
"Manage Blocklist (" + std::to_string(blockList.entries().size()) + ")",
false});
optionsList.push_back({11, kIconPlus, "Block a Number...", false});
} else if (mode == ViewMode::ThreadView) { } else if (mode == ViewMode::ThreadView) {
optionsList.push_back({3, kIconSend, "Reply / Compose"}); bool isBlk = blockList.isBlocked(activeNumber);
optionsList.push_back({4, kIconPhone, "Call Contact"}); optionsList.push_back({3, kIconSend, "Reply / Compose", false});
optionsList.push_back({4, kIconPhone, "Call Contact", false});
if (!threadMessages.empty()) { if (!threadMessages.empty()) {
optionsList.push_back({7, kIconCopy, "Forward Selected Msg"}); optionsList.push_back({7, kIconCopy, "Forward Selected Msg", false});
} }
optionsList.push_back({5, kIconUser, "Save / View Contact"}); if (isBlk || viewingSpamFolder) {
optionsList.push_back({6, kIconTrash, "Delete Conversation"}); optionsList.push_back({8, kIconCheck, "Unblock & Restore", false});
} else {
optionsList.push_back({8, kIconBan, "Block Number (Spam)", true});
}
optionsList.push_back({5, kIconUser, "Save / View Contact", false});
optionsList.push_back({6, kIconTrash, "Delete Conversation", true});
optionsList.push_back(
{10, kIconBan,
"Manage Blocklist (" + std::to_string(blockList.entries().size()) + ")",
false});
} }
selectedOption = 0; selectedOption = 0;
}; };
@@ -462,9 +521,13 @@ int runSmsApp(int argc, char** argv) {
XDrawLine(dpy, pix, gc, 0, 37, kWinWidth, 37); XDrawLine(dpy, pix, gc, 0, 37, kWinWidth, 37);
if (searchQuery.empty()) { if (searchQuery.empty()) {
drawPango(layoutIcon, &colYellow, 8, 2, 34, kIconSms); drawPango(layoutIcon, viewingSpamFolder ? &colRed : &colYellow, 8, 2,
drawPango(layoutMain, &colWhite, 30, 2, 34, "Messages", 145); 34, viewingSpamFolder ? kIconBan : kIconSms);
std::string cnt = "[" + std::to_string(allThreads.size()) + "]"; std::string hdrTitle =
viewingSpamFolder ? "Spam / Blocked" : "Messages";
drawPango(layoutMain, viewingSpamFolder ? &colRed : &colWhite, 30, 2,
34, hdrTitle, 145);
std::string cnt = "< [" + std::to_string(allThreads.size()) + "] >";
pango_layout_set_width(layoutSmall, -1); pango_layout_set_width(layoutSmall, -1);
pango_layout_set_text(layoutSmall, cnt.c_str(), pango_layout_set_text(layoutSmall, cnt.c_str(),
static_cast<int>(cnt.size())); static_cast<int>(cnt.size()));
@@ -489,9 +552,15 @@ int runSmsApp(int argc, char** argv) {
if (filteredThreads.empty()) { if (filteredThreads.empty()) {
drawPango(layoutMain, &colMuted, 16, 110, 24, drawPango(layoutMain, &colMuted, 16, 110, 24,
searchQuery.empty() ? "No messages yet" : "No matches", 208); searchQuery.empty()
? (viewingSpamFolder ? "No blocked messages"
: "No messages yet")
: "No matches",
208);
drawPango(layoutSmall, &colBlue, 16, 138, 20, drawPango(layoutSmall, &colBlue, 16, 138, 20,
"Press Left Softkey for New SMS", 208); viewingSpamFolder ? "Left/Right: Switch to Inbox"
: "Left Softkey: Options / Blocklist",
208);
} else { } else {
int scroll = 0; int scroll = 0;
if (selectedThread >= visibleRows) { if (selectedThread >= visibleRows) {
@@ -515,17 +584,22 @@ int runSmsApp(int argc, char** argv) {
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
} }
const char* ic = th.hasDraft const char* ic =
? kIconEdit th.isBlocked
: (th.lastIncoming ? kIconInCall : kIconOutCall); ? kIconBan
XftColor* icCol = th.hasDraft : (th.hasDraft ? kIconEdit
? &colYellow : (th.lastIncoming ? kIconInCall : kIconOutCall));
: (th.lastIncoming ? &colGreen : &colBlue); XftColor* icCol =
th.isBlocked
? &colRed
: (th.hasDraft ? &colYellow
: (th.lastIncoming ? &colGreen : &colBlue));
drawPango(layoutIcon, icCol, 6, ry + 1, 20, ic); drawPango(layoutIcon, icCol, 6, ry + 1, 20, ic);
std::string title = std::string title =
th.contactName.empty() ? th.number : th.contactName; th.contactName.empty() ? th.number : th.contactName;
drawPango(layoutMain, &colWhite, 26, ry + 1, 20, title, 128); drawPango(layoutMain, th.isBlocked ? &colRed : &colWhite, 26, ry + 1,
20, title, 128);
if (!th.lastDisplayTime.empty()) { if (!th.lastDisplayTime.empty()) {
pango_layout_set_width(layoutSmall, -1); pango_layout_set_width(layoutSmall, -1);
@@ -807,18 +881,89 @@ int runSmsApp(int argc, char** argv) {
const int listY = 38; const int listY = 38;
const int rowH = 36; const int rowH = 36;
for (size_t i = 0; i < optionsList.size(); ++i) { const int visRows = 6;
int ry = listY + static_cast<int>(i) * rowH; int scroll = 0;
bool isSel = (static_cast<int>(i) == selectedOption); if (selectedOption >= visRows) {
scroll = selectedOption - visRows + 1;
}
for (int i = 0; i < visRows; ++i) {
int idx = scroll + i;
if (idx >= static_cast<int>(optionsList.size())) break;
int ry = listY + i * rowH;
bool isSel = (idx == selectedOption);
XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain); XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain);
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
drawPango(layoutIcon, isSel ? &colYellow : &colBlue, 10, ry, rowH, XftColor* icCol =
optionsList[i].icon); optionsList[idx].danger ? &colRed : (isSel ? &colYellow : &colBlue);
drawPango(layoutMain, &colWhite, 34, ry, rowH, optionsList[i].label, XftColor* txCol = optionsList[idx].danger ? &colRed : &colWhite;
198); drawPango(layoutIcon, icCol, 10, ry, rowH, optionsList[idx].icon);
drawPango(layoutMain, txCol, 34, ry, rowH, optionsList[idx].label, 198);
} }
drawSoftkeyBar("Select", "Select", "Back"); drawSoftkeyBar("Select", "Select", "Back");
} else if (mode == ViewMode::Blocklist) {
XSetForeground(dpy, gc, pt.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34);
drawPango(layoutIcon, &colRed, 8, 0, 34, kIconBan);
std::string hdr =
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
drawPango(layoutMain, &colWhite, 30, 0, 34, hdr, 200);
const auto& entries = blockList.entries();
if (entries.empty()) {
drawPango(layoutIcon, &colMuted, 112, 94, 24, kIconShield);
drawPango(layoutMain, &colMuted, 44, 124, 24, "Blocklist is empty",
180);
drawPango(layoutSmall, &colBlue, 22, 152, 20,
"Left Softkey: Block a Number", 204);
} else {
const int listY = 36;
const int rowH = 38;
const int visRows = 6;
int scroll = 0;
if (blockSelected >= visRows) {
scroll = blockSelected - visRows + 1;
}
for (int i = 0; i < visRows; ++i) {
int idx = scroll + i;
if (idx >= static_cast<int>(entries.size())) break;
const auto& e = entries[idx];
int ry = listY + i * rowH;
bool isSel = (idx == blockSelected);
XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain);
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
std::string cname = book.resolveName(e.number);
std::string topLine = cname.empty() ? e.number : cname;
std::string subLine =
cname.empty() ? e.label : (e.number + " (" + e.label + ")");
drawPango(layoutIcon, &colRed, 8, ry + 1, 20, kIconBan);
drawPango(layoutMain, &colWhite, 28, ry + 1, 20, topLine, 204);
drawPango(layoutSmall, isSel ? &colLight : &colMuted, 28, ry + 20, 16,
subLine, 204);
}
}
drawSoftkeyBar("Add New", entries.empty() ? "" : "Unblock", "Back");
} else if (mode == ViewMode::AddBlock) {
XSetForeground(dpy, gc, pt.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34);
drawPango(layoutIcon, &colRed, 8, 0, 34, kIconBan);
drawPango(layoutMain, &colWhite, 30, 0, 34, "Block Phone Number", 200);
drawPango(layoutSmall, &colMuted, 12, 54, 20,
"Enter number or prefix (e.g. +1800*):", 216);
XSetForeground(dpy, gc, pt.spec.bgInputFocus);
XFillRectangle(dpy, pix, gc, 12, 80, kWinWidth - 24, 34);
XSetForeground(dpy, gc, pt.spec.accentAlt);
XDrawRectangle(dpy, pix, gc, 12, 80, kWinWidth - 25, 33);
std::string disp = addBlockInput + "|";
drawPango(layoutMain, &colWhite, 18, 83, 28, disp, kWinWidth - 36);
drawPango(layoutSmall, &colMuted, 12, 124, 20,
"0-9 / * / #: Digits #: + prefix", 216);
drawPango(layoutSmall, &colMuted, 12, 146, 20,
"BackSpace/Left: Delete digit", 216);
drawSoftkeyBar("Block", "Save", "Cancel");
} else if (mode == ViewMode::ConfirmDelete) { } else if (mode == ViewMode::ConfirmDelete) {
XSetForeground(dpy, gc, pt.spec.bgHeader); XSetForeground(dpy, gc, pt.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36);
@@ -997,6 +1142,49 @@ int runSmsApp(int argc, char** argv) {
} else if (optId == 7 && !threadMessages.empty()) { } else if (optId == 7 && !threadMessages.empty()) {
std::string body = threadMessages[selectedMsg].text; std::string body = threadMessages[selectedMsg].text;
openComposeView("", body); openComposeView("", body);
} else if (optId == 8) {
std::string target = (prevMode == ViewMode::ThreadView)
? activeNumber
: (!filteredThreads.empty()
? filteredThreads[selectedThread].number
: "");
if (!target.empty()) {
if (blockList.isBlocked(target) || viewingSpamFolder) {
blockList.unblockNumber(target);
moveSmsThreadBetweenDirs(target, getBlockDirPath(),
getModemLogDirPath());
std::lock_guard<std::mutex> lk(sendMutex);
statusBanner = "Unblocked & restored " + target;
statusBannerUntilMs = nowMs() + 2000;
} else {
std::string cname = book.resolveName(target);
blockList.blockNumber(target, cname.empty() ? "SMS Spam" : cname);
moveSmsThreadBetweenDirs(target, getModemLogDirPath(),
getBlockDirPath());
std::lock_guard<std::mutex> lk(sendMutex);
statusBanner = "Blocked " + target;
statusBannerUntilMs = nowMs() + 2000;
}
reloadAllData(true);
}
mode = ViewMode::Threads;
} else if (optId == 9) {
viewingSpamFolder = !viewingSpamFolder;
selectedThread = 0;
reloadAllData(true);
mode = ViewMode::Threads;
} else if (optId == 10) {
blockList.load();
blockSelected = 0;
mode = ViewMode::Blocklist;
} else if (optId == 11) {
std::string target = (prevMode == ViewMode::ThreadView)
? activeNumber
: (!filteredThreads.empty()
? filteredThreads[selectedThread].number
: "");
addBlockInput = target;
mode = ViewMode::AddBlock;
} }
}; };
@@ -1010,6 +1198,10 @@ int runSmsApp(int argc, char** argv) {
if (!searchQuery.empty()) { if (!searchQuery.empty()) {
popUtf8Char(searchQuery); popUtf8Char(searchQuery);
updateFilter(); updateFilter();
} else if (viewingSpamFolder) {
viewingSpamFolder = false;
selectedThread = 0;
updateFilter();
} else { } else {
hideWindow(); hideWindow();
return; return;
@@ -1066,10 +1258,40 @@ int runSmsApp(int argc, char** argv) {
} else { } else {
mode = prevMode; mode = prevMode;
} }
} else if (mode == ViewMode::Blocklist) {
if (isLeft) {
addBlockInput.clear();
mode = ViewMode::AddBlock;
} else {
reloadAllData(true);
mode = prevMode;
}
} else if (mode == ViewMode::AddBlock) {
if (isLeft) {
std::string num = trimStr(addBlockInput);
if (!normalizePhone(num).empty()) {
blockList.blockNumber(num, "SMS Blocklist");
moveSmsThreadBetweenDirs(num, getModemLogDirPath(),
getBlockDirPath());
reloadAllData(true);
std::lock_guard<std::mutex> lk(sendMutex);
statusBanner = "Blocked " + num;
statusBannerUntilMs = nowMs() + 2000;
}
mode = ViewMode::Blocklist;
} else {
if (!addBlockInput.empty()) {
popUtf8Char(addBlockInput);
} else {
mode = ViewMode::Blocklist;
}
}
} else if (mode == ViewMode::ConfirmDelete) { } else if (mode == ViewMode::ConfirmDelete) {
if (isLeft) { if (isLeft) {
if (confirmSelected == 1) { if (confirmSelected == 1) {
deleteSmsThread(activeNumber); deleteSmsThread(activeNumber, viewingSpamFolder
? getBlockDirPath()
: getModemLogDirPath());
reloadAllData(true); reloadAllData(true);
mode = ViewMode::Threads; mode = ViewMode::Threads;
} else { } else {
@@ -1189,7 +1411,13 @@ int runSmsApp(int argc, char** argv) {
} }
if (mode == ViewMode::Threads) { if (mode == ViewMode::Threads) {
if (sym == XK_Up || sym == XK_KP_Up) { if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Right ||
sym == XK_KP_Right) {
viewingSpamFolder = !viewingSpamFolder;
selectedThread = 0;
updateFilter();
drawUI();
} else if (sym == XK_Up || sym == XK_KP_Up) {
if (!filteredThreads.empty()) { if (!filteredThreads.empty()) {
selectedThread = selectedThread =
(selectedThread - 1 + static_cast<int>(filteredThreads.size())) % (selectedThread - 1 + static_cast<int>(filteredThreads.size())) %
@@ -1346,6 +1574,57 @@ int runSmsApp(int argc, char** argv) {
} else if (sym == XK_Return || sym == XK_KP_Enter) { } else if (sym == XK_Return || sym == XK_KP_Enter) {
handleSoftkeyAction(true); handleSoftkeyAction(true);
} }
} else if (mode == ViewMode::Blocklist) {
if (sym == XK_Up || sym == XK_KP_Up) {
if (!blockList.entries().empty()) {
int n = static_cast<int>(blockList.entries().size());
blockSelected = (blockSelected + n - 1) % n;
drawUI();
}
} else if (sym == XK_Down || sym == XK_KP_Down) {
if (!blockList.entries().empty()) {
int n = static_cast<int>(blockList.entries().size());
blockSelected = (blockSelected + 1) % n;
drawUI();
}
} else if (sym == XK_Return || sym == XK_KP_Enter) {
if (!blockList.entries().empty() && blockSelected >= 0 &&
blockSelected < static_cast<int>(blockList.entries().size())) {
std::string num = blockList.entries()[blockSelected].number;
blockList.unblockNumber(num);
moveSmsThreadBetweenDirs(num, getBlockDirPath(),
getModemLogDirPath());
reloadAllData(true);
{
std::lock_guard<std::mutex> lk(sendMutex);
statusBanner = "Unblocked " + num;
statusBannerUntilMs = nowMs() + 1800;
}
drawUI();
}
}
} else if (mode == ViewMode::AddBlock) {
if (sym == XK_Left || sym == XK_KP_Left) {
if (!addBlockInput.empty()) {
popUtf8Char(addBlockInput);
drawUI();
}
} else if (sym == XK_Return || sym == XK_KP_Enter) {
handleSoftkeyAction(true);
} else {
char d = mapDialKey(sym, kc, ev.xkey.state);
if (d == '#') {
if (addBlockInput.empty()) {
addBlockInput.push_back('+');
} else {
addBlockInput.push_back('#');
}
drawUI();
} else if (d != '\0') {
addBlockInput.push_back(d);
drawUI();
}
}
} else if (mode == ViewMode::ConfirmDelete) { } else if (mode == ViewMode::ConfirmDelete) {
if (sym == XK_Up || sym == XK_Down || sym == XK_KP_Up || if (sym == XK_Up || sym == XK_Down || sym == XK_KP_Up ||
sym == XK_KP_Down) { sym == XK_KP_Down) {
@@ -0,0 +1,39 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef ARRAY_H
#define ARRAY_H
#include <cstddef>
#include "uncopyable.h"
template<typename T>
class Array : Uncopyable {
T *a;
std::size_t sz;
public:
explicit Array(const std::size_t size = 0) : a(size ? new T[size] : 0), sz(size) {}
~Array() { delete []a; }
void reset(const std::size_t size = 0) { delete []a; a = size ? new T[size] : 0; sz = size; }
std::size_t size() const { return sz; }
T * get() const { return a; }
operator T*() const { return a; }
};
#endif
+29
View File
@@ -0,0 +1,29 @@
/***************************************************************************
* Copyright (C) 2009 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef UNCOPYABLE_H
#define UNCOPYABLE_H
class Uncopyable {
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
public:
Uncopyable() {}
};
#endif
+162
View File
@@ -0,0 +1,162 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef GAMBATTE_H
#define GAMBATTE_H
#include "inputgetter.h"
#ifdef HAVE_NETWORK
#include "serial_io.h"
#endif
#include "gbint.h"
#include <string>
#include <cstddef>
namespace gambatte {
#if defined(VIDEO_RGB565) || defined(VIDEO_ABGR1555)
typedef uint16_t video_pixel_t;
#else
typedef uint_least32_t video_pixel_t;
#endif
enum { BG_PALETTE = 0, SP1_PALETTE = 1, SP2_PALETTE = 2 };
class GB {
public:
GB();
~GB();
enum LoadFlag {
FORCE_DMG = 1, /**< Treat the ROM as not having CGB support regardless of what its header advertises. */
GBA_CGB = 2, /**< Use GBA intial CPU register values when in CGB mode. */
MULTICART_COMPAT = 4, /**< Use heuristics to detect and support some multicart MBCs disguised as MBC1. */
FORCE_CGB = 8
};
int load(const void *romdata, unsigned size, unsigned flags = 0);
/** Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer,
* or until a video frame has been drawn.
*
* There are 35112 stereo sound samples in a video frame.
* May run for uptil 2064 stereo samples too long.
* EDIT: Due to internal emulator bugs, may in fact run for
* an arbitrary number of samples...
* A stereo sample consists of two native endian 2s complement 16-bit PCM samples,
* with the left sample preceding the right one. Usually casting soundBuf to/from
* short* is OK and recommended. The reason for not using a short* in the interface
* is to avoid implementation-defined behaviour without compromising performance.
*
* Returns early when a new video frame has finished drawing in the video buffer,
* such that the caller may update the video output before the frame is overwritten.
* The return value indicates whether a new video frame has been drawn, and the
* exact time (in number of samples) at which it was drawn.
*
* @param videoBuf 160x144 RGB32 (native endian) video frame buffer or 0
* @param pitch distance in number of pixels (not bytes) from the start of one line to the next in videoBuf.
* @param soundBuf buffer with space >= samples + 2064
* @param soundBufSize actual size of soundBuf buffer
* @param samples in: number of stereo samples to produce, out: actual number of samples produced
* @return sample number at which the video frame was produced. -1 means no frame was produced.
*/
long runFor(gambatte::video_pixel_t *videoBuf, int pitch,
gambatte::uint_least32_t *soundBuf, std::size_t soundBufSize, unsigned &samples);
/** Reset to initial state.
* Equivalent to reloading a ROM image, or turning a Game Boy Color off and on again.
*/
void reset();
/** @param palNum 0 <= palNum < 3. One of BG_PALETTE, SP1_PALETTE and SP2_PALETTE.
* @param colorNum 0 <= colorNum < 4
*/
void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32);
/** Sets the callback used for getting input state. */
void setInputGetter(InputGetter *getInput);
/** Sets the callback used for getting the bootloader data. */
void setBootloaderGetter(bool (*getter)(void *userdata, bool isgbc, uint8_t *data, uint32_t buf_size));
#ifdef HAVE_NETWORK
/** Sets the callback used for transferring serial data. */
void setSerialIO(SerialIO *serial_io);
#endif
/** Sets the directory used for storing save data. The default is the same directory as the ROM Image file. */
void setSaveDir(const std::string &sdir);
void *savedata_ptr();
unsigned savedata_size();
void *rtcdata_ptr();
unsigned rtcdata_size();
/** Returns true if the currently loaded ROM image is treated as having CGB support. */
bool isCgb() const;
/** Returns true if a ROM image is loaded. */
bool isLoaded() const;
void saveState(void *data);
/* Load a savestate previously produced by saveState. The size
* parameter must match the buffer length so the parser can
* reject malformed states without reading past the end of the
* buffer. Returns true on success. */
bool loadState(const void *data, size_t size);
size_t stateSize() const;
void setColorCorrection(bool enable);
void setColorCorrectionMode(unsigned colorCorrectionMode);
void setColorCorrectionBrightness(float colorCorrectionBrightness);
void setDarkFilterLevel(unsigned darkFilterLevel);
video_pixel_t gbcToRgb32(const unsigned bgr15);
/** Set Game Genie codes to apply to currently loaded ROM image. Cleared on ROM load.
* @param codes Game Genie codes in format HHH-HHH-HHH;HHH-HHH-HHH;... where H is [0-9]|[A-F]
*/
void setGameGenie(const std::string &codes);
/** Set Game Shark codes to apply to currently loaded ROM image. Cleared on ROM load.
* @param codes Game Shark codes in format 01HHHHHH;01HHHHHH;... where H is [0-9]|[A-F]
*/
void setGameShark(const std::string &codes);
void clearCheats();
#ifdef __LIBRETRO__
void *vram_ptr() const;
void *rambank0_ptr() const;
void *rambank1_ptr() const;
void *rambank2_ptr() const;
void *bankedram_ptr() const;
void *rombank0_ptr() const;
void *rombank1_ptr() const;
void *zeropage_ptr() const;
void *oamram_ptr() const;
#endif
private:
struct Priv;
Priv *const p_;
void loadState(const std::string &filepath, bool osdMessage);
GB(const GB &);
GB & operator=(const GB &);
};
}
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef GAMBATTE_LOG_H
#define GAMBATTE_LOG_H
enum retro_log_level {
RETRO_LOG_DEBUG = 0,
RETRO_LOG_INFO,
RETRO_LOG_WARN,
RETRO_LOG_ERROR
};
static inline void gambatte_log(enum retro_log_level level, const char *format, ...) {
(void)level;
(void)format;
}
#endif
+62
View File
@@ -0,0 +1,62 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef GAMBATTE_INT_H
#define GAMBATTE_INT_H
#ifdef HAVE_CSTDINT
#include <cstdint>
namespace gambatte {
using std::uint_least32_t;
using std::uint_least16_t;
}
#elif defined(HAVE_STDINT_H)
#include <stdint.h>
namespace gambatte {
using ::uint_least32_t;
using ::uint_least16_t;
}
#else
namespace gambatte {
#ifdef CHAR_LEAST_32
typedef unsigned char uint_least32_t;
#elif defined(SHORT_LEAST_32)
typedef unsigned short uint_least32_t;
#elif defined(INT_LEAST_32)
typedef unsigned uint_least32_t;
#else
typedef unsigned long uint_least32_t;
#endif
#ifdef CHAR_LEAST_16
typedef unsigned char uint_least16_t;
#else
typedef unsigned short uint_least16_t;
#endif
}
#endif
#endif
+33
View File
@@ -0,0 +1,33 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef GAMBATTE_INPUTGETTER_H
#define GAMBATTE_INPUTGETTER_H
namespace gambatte {
class InputGetter {
public:
enum { A = 0x01, B = 0x02, SELECT = 0x04, START = 0x08, RIGHT = 0x10, LEFT = 0x20, UP = 0x40, DOWN = 0x80 };
virtual ~InputGetter() {};
/** @return A|B|SELECT|START|RIGHT|LEFT|UP|DOWN if those buttons are pressed. */
virtual unsigned operator()() = 0;
};
}
#endif
+113
View File
@@ -0,0 +1,113 @@
#include <stdint.h>
#include <cstring>
#include <string>
#include "bootloader.h"
namespace gambatte {
Bootloader::Bootloader()
: addrspace_start(NULL)
, bootloadersize(0)
, has_called_FF50(false)
, get_raw_bootloader_data(NULL)
, using_bootloader(false)
{
/* bootromswapspace and rombackup are intentionally left
* uninitialized for performance (0x900 bytes each); they
* are written before any read in load(). All scalar members
* are now initialized so observers (e.g. choosebank() called
* before a successful load) cannot read garbage. */
}
void Bootloader::patch_gbc_to_gba_mode() {
/*moves one jump over another and puts ld b,0x01 into the original position*/
uint16_t patchloc = 0xF2;
uint8_t patch[0x7] = {0xCD,0xD0,0x05/*<-call systemsetup*/,0x06,0x01/*<-ld b,0x1*/,0x00/*<-nop*/,0x00/*<-nop*/};
std::memcpy(bootromswapspace + patchloc, patch, 0x7);
}
void Bootloader::load(bool isgbc, bool isgba) {
if (get_raw_bootloader_data == NULL) {
using_bootloader = false;
return;
}
//the gba only uses the gbc bios
if (isgba)
isgbc = true;
bool bootloaderavail = get_raw_bootloader_data((void*)this, isgbc, bootromswapspace, 0x900/*buf_size*/);
if (!bootloaderavail) {
using_bootloader = false;
return;
}
if (isgbc)
bootloadersize = 0x900;
else
bootloadersize = 0x100;
if (isgba)//patch bootloader to fake gba mode
patch_gbc_to_gba_mode();
//backup rom segment that is shared with bootloader
std::memcpy(rombackup, (uint8_t*)addrspace_start, bootloadersize);
//put back cartridge data in a 256 byte window of the bios that is not mapped(GBC only)
if (isgbc)
std::memcpy(bootromswapspace + 0x100, rombackup + 0x100, 0x100);
//put bootloader in main memory
std::memcpy((uint8_t*)addrspace_start, bootromswapspace, bootloadersize);
using_bootloader = true;
}
void Bootloader::reset() {
bootloadersize = 0;
has_called_FF50 = false;
addrspace_start = NULL;
using_bootloader = false;
}
void Bootloader::set_bootloader_getter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size)) {
get_raw_bootloader_data = getter;
}
void Bootloader::set_address_space_start(void* start) {
addrspace_start = start;
}
void Bootloader::choosebank(bool inbootloader) {
//inbootloader = (state.mem.ioamhram.get()[0x150] != 0xFF);//do not uncomment this is just for reference
if (using_bootloader) {
//switching from game to bootloader with savestate
if (inbootloader && has_called_FF50)
uncall_FF50();
//switching from bootloader to game with savestate
else if (!inbootloader && !has_called_FF50)
call_FF50();
//switching from game to game or bootloader to bootloader needs no changes
}
}
void Bootloader::call_FF50() {
if (!has_called_FF50 && using_bootloader) {
//put rom back in main memory when bootloader has finished
std::memcpy((uint8_t*)addrspace_start, rombackup, bootloadersize);
has_called_FF50 = true;
}
}
//this is a developer function only,a real gameboy can never undo calling 0xFF50,this function is for savestate functionality
void Bootloader::uncall_FF50() {
std::memcpy((uint8_t*)addrspace_start, bootromswapspace, bootloadersize);
has_called_FF50 = false;
}
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef BOOTLOADER_H
#define BOOTLOADER_H
#include <stdint.h>
#include <string>
namespace gambatte {
class Bootloader {
private:
uint8_t bootromswapspace[0x900];
uint8_t rombackup[0x900];
void* addrspace_start;
unsigned int bootloadersize;
bool has_called_FF50;
//Set this to NULL or return false if you want to ignore bootloaders completely
//(initial value is NULL and so the bootloader is disabled by default)
bool (*get_raw_bootloader_data)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size);
void patch_gbc_to_gba_mode();
void uncall_FF50();
public:
bool using_bootloader;
Bootloader();
void load(bool isgbc,bool isgba);
void reset();
void set_bootloader_getter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size));
void set_address_space_start(void* start);
void choosebank(bool inbootloader);
void call_FF50();
};
}
#endif
+10
View File
@@ -0,0 +1,10 @@
#ifndef COUNTERDEF_H
#define COUNTERDEF_H
namespace gambatte {
enum { disabled_time = 0xfffffffful };
}
#endif
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef CPU_H
#define CPU_H
#include "gambatte.h"
#include "gambatte-memory.h"
#include "savestate.h"
namespace gambatte {
#ifdef HAVE_NETWORK
class SerialIO;
#endif
class CPU {
public:
CPU();
long runFor(unsigned long cycles);
void setStatePtrs(SaveState &state);
void saveState(SaveState &state);
void loadState(SaveState const &state);
#if 0
void loadSavedata() { mem_.loadSavedata(); }
void saveSavedata() { mem_.saveSavedata(); }
#endif
#ifdef __LIBRETRO__
void *savedata_ptr() { return mem_.savedata_ptr(); }
unsigned savedata_size() { return mem_.savedata_size(); }
void *rtcdata_ptr() { return mem_.rtcdata_ptr(); }
unsigned rtcdata_size() { return mem_.rtcdata_size(); }
void clearCheats() { mem_.clearCheats(); }
void *vram_ptr() const { return mem_.vram_ptr(); }
void *rambank0_ptr() const { return mem_.rambank0_ptr(); }
void *rambank1_ptr() const { return mem_.rambank1_ptr(); }
void *rambank2_ptr() const { return mem_.rambank2_ptr(); }
void *bankedram_ptr() const { return mem_.bankedram_ptr(); }
void *rombank0_ptr() const { return mem_.rombank0_ptr(); }
void *rombank1_ptr() const { return mem_.rombank1_ptr(); }
void *zeropage_ptr() const { return mem_.zeropage_ptr(); }
void *oamram_ptr() const { return mem_.oamram_ptr(); }
#endif
void setVideoBuffer(video_pixel_t *videoBuf, std::ptrdiff_t pitch) {
mem_.setVideoBuffer(videoBuf, pitch);
}
void setInputGetter(InputGetter *getInput) {
mem_.setInputGetter(getInput);
}
#ifdef HAVE_NETWORK
void setSerialIO(SerialIO *serial_io) {
mem_.setSerialIO(serial_io);
}
#endif
void setSaveDir(std::string const &sdir) {
mem_.setSaveDir(sdir);
}
std::string const saveBasePath() const {
return mem_.saveBasePath();
}
int load(const void *romdata, unsigned int romsize, unsigned int forceModel, bool multicartCompat) {
return mem_.loadROM(romdata, romsize, forceModel, multicartCompat);
}
#if 0
bool loaded() const { return mem_.loaded(); }
#endif
void setSoundBuffer(uint_least32_t *buf, std::size_t size) { mem_.setSoundBuffer(buf, size); }
std::size_t fillSoundBuffer() { return mem_.fillSoundBuffer(cycleCounter_); }
bool isCgb() const { return mem_.isCgb(); }
void setDmgPaletteColor(int palNum, int colorNum, unsigned long rgb32) {
mem_.setDmgPaletteColor(palNum, colorNum, rgb32);
}
void setGameGenie(std::string const &codes) { mem_.setGameGenie(codes); }
void setGameShark(std::string const &codes) { mem_.setGameShark(codes); }
Memory mem_;
private:
unsigned long cycleCounter_;
unsigned short pc_;
unsigned short sp;
unsigned hf1, hf2, zf, cf;
unsigned char a_, b, c, d, e, /*f,*/ h, l;
bool skip_;
void process(unsigned long cycles);
};
}
#endif
File diff suppressed because it is too large Load Diff
+226
View File
@@ -0,0 +1,226 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef MEMORY_H
#define MEMORY_H
#include "mem/cartridge.h"
#include "interrupter.h"
#include "bootloader.h"
#include "sound.h"
#include "tima.h"
#include "video.h"
namespace gambatte {
class InputGetter;
#ifdef HAVE_NETWORK
class SerialIO;
#endif
class Memory {
public:
Bootloader bootloader;
explicit Memory(Interrupter const &interrupter);
bool loaded() const { return cart_.loaded(); }
void setStatePtrs(SaveState &state);
unsigned long saveState(SaveState &state, unsigned long cc);
void loadState(SaveState const &state);
#ifdef __LIBRETRO__
void *savedata_ptr() { return cart_.savedata_ptr(); }
unsigned savedata_size() { return cart_.savedata_size(); }
void *rtcdata_ptr() { return cart_.rtcdata_ptr(); }
unsigned rtcdata_size() { return cart_.rtcdata_size(); }
void display_setColorCorrection(bool enable) { lcd_.setColorCorrection(enable); }
void display_setColorCorrectionMode(unsigned colorCorrectionMode) { lcd_.setColorCorrectionMode(colorCorrectionMode); }
void display_setColorCorrectionBrightness(float colorCorrectionBrightness) { lcd_.setColorCorrectionBrightness(colorCorrectionBrightness); }
void display_setDarkFilterLevel(unsigned darkFilterLevel) { lcd_.setDarkFilterLevel(darkFilterLevel); }
video_pixel_t display_gbcToRgb32(const unsigned bgr15) { return lcd_.gbcToRgb32(bgr15); }
void clearCheats() { cart_.clearCheats(); interrupter_.clearCheats(); }
void *vram_ptr() const { return cart_.vramdata(); }
void *rambank0_ptr() const { return cart_.wramdata(0); }
void *rambank1_ptr() const { return cart_.wramdata(0) + 0x1000; }
void *rambank2_ptr() const { return cart_.wramdata(0) + 0x2000; }
void *bankedram_ptr() const { return cart_.wramdata(1); }
void *rombank0_ptr() const { return cart_.romdata(0); }
void *rombank1_ptr() const { return cart_.romdata(0) + 0x4000; }
void *zeropage_ptr() const { return (void*)(ioamhram_ + 0x0180); }
void *oamram_ptr() const { return (void*)ioamhram_; }
#else
void loadSavedata() { cart_.loadSavedata(); }
void saveSavedata() { cart_.saveSavedata(); }
#endif
std::string const saveBasePath() const { return cart_.saveBasePath(); }
unsigned long stop(unsigned long cycleCounter);
bool isCgb() const { return lcd_.isCgb(); }
bool ime() const { return intreq_.ime(); }
bool halted() const { return intreq_.halted(); }
unsigned long nextEventTime() const { return intreq_.minEventTime(); }
bool isActive() const { return intreq_.eventTime(intevent_end) != disabled_time; }
long cyclesSinceBlit(unsigned long cc) const
{
if (cc < intreq_.eventTime(intevent_blit))
return -1;
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
return (cc - intreq_.eventTime(intevent_blit)) >> is_doublespeed;
}
void halt() { intreq_.halt(); }
void ei(unsigned long cycleCounter) { if (!ime()) { intreq_.ei(cycleCounter); } }
void di() { intreq_.di(); }
unsigned ff_read(unsigned p, unsigned long cc) {
return p < 0x80 ? nontrivial_ff_read(p, cc) : ioamhram_[p + 0x100];
}
unsigned read(unsigned p, unsigned long cc) {
/* Sachen MMC1 carts need to count CPU reads of 0x0100..0x01FF
* so the mapper can leave its boot-time locked state
* between the bootstrap's logo-display pass (48 cart
* reads of 0x0104..0x0133) and its logo-verify pass
* (another 48 reads of the same range). The counter
* pointer is null for every other cartridge, so the
* branch below is a single predictable null-check on
* the hot path. The unlock fires AFTER the 48th locked
* read has returned its byte, so the display pass sees a
* full Sachen-logo sequence and the very next read
* (first byte of the verify pass) sees the unlocked
* Nintendo bytes. */
const unsigned char *const rm = cart_.rmem(p >> 12);
const unsigned value = rm ? rm[p] : nontrivial_read(p, cc);
if (sachenLockCounter_ && (p & 0xFF00) == 0x0100) {
if (*sachenLockCounter_ < 48) {
if (++*sachenLockCounter_ == 48)
cart_.onSachenUnlock();
}
}
return value;
}
void write(unsigned p, unsigned data, unsigned long cc) {
if (cart_.wmem(p >> 12)) {
cart_.wmem(p >> 12)[p] = data;
} else
nontrivial_write(p, data, cc);
}
void ff_write(unsigned p, unsigned data, unsigned long cc) {
if (p - 0x80u < 0x7Fu) {
ioamhram_[p + 0x100] = data;
} else
nontrivial_ff_write(p, data, cc);
}
#ifdef HAVE_NETWORK
void startSerialTransfer(unsigned long cycleCounter, unsigned char data, bool fastCgb);
#endif
unsigned long event(unsigned long cycleCounter);
unsigned long resetCounters(unsigned long cycleCounter);
void setSaveDir(std::string const &dir) { cart_.setSaveDir(dir); }
void setInputGetter(InputGetter *getInput) { getInput_ = getInput; }
#ifdef HAVE_NETWORK
void setSerialIO(SerialIO* serial_io) { serial_io_ = serial_io; }
#endif
void setEndtime(unsigned long cc, unsigned long inc);
void setSoundBuffer(uint_least32_t *buf, std::size_t size) { psg_.setBuffer(buf, size); }
std::size_t fillSoundBuffer(unsigned long cc);
void setVideoBuffer(video_pixel_t *videoBuf, std::ptrdiff_t pitch) {
lcd_.setVideoBuffer(videoBuf, pitch);
}
void setDmgPaletteColor(int palNum, int colorNum, unsigned long rgb32) {
lcd_.setDmgPaletteColor(palNum, colorNum, rgb32);
}
void setGameGenie(std::string const &codes) { cart_.setGameGenie(codes); }
void setGameShark(std::string const &codes) { interrupter_.setGameShark(codes); }
#ifdef HAVE_NETWORK
void checkSerial(unsigned long cc);
#endif
void updateInput();
int loadROM(const void *romdata, unsigned int romsize, unsigned int forceModel, const bool multicartCompat);
/* Forwarders for unlicensed-mapper hooks. Currently only Sachen
* MMC1 carts use them; for every other cartridge type these are
* no-ops and isSachen() returns false. Called by
* GB::Priv::full_init after the bootloader has been installed
* (or skipped). */
bool isSachen() const { return cart_.isSachen(); }
void sachenLockSetup(bool bootloaderUsed) {
cart_.sachenLockSetup(bootloaderUsed);
/* Pick up the MBC's lock-counter address now that the cart
* has decided whether to enter the locked state. For a
* Sachen cart, sachenLockCounterPtr() returns a real
* address; for any other cart it returns null and the read
* fast path stays in single-branch territory. */
sachenLockCounter_ = cart_.sachenLockCounterPtr();
}
void onSachenUnlock() { cart_.onSachenUnlock(); }
private:
Cartridge cart_;
unsigned char ioamhram_[0x200];
/* Pointer to the Sachen MBC's read counter, or null for any
* non-Sachen cart. When non-null, Memory::read bumps it on
* every CPU read of 0x0100..0x01FF and signals the cart on
* the 48th such read so the mapper can leave its locked
* state between the bootstrap's display and verify passes. */
unsigned char *sachenLockCounter_;
#ifdef HAVE_NETWORK
unsigned char serialize_value_;
bool serialize_is_fastcgb_;
SerialIO *serial_io_;
#endif
InputGetter *getInput_;
unsigned long divLastUpdate_;
unsigned long lastOamDmaUpdate_;
InterruptRequester intreq_;
Tima tima_;
LCD lcd_;
PSG psg_;
Interrupter interrupter_;
unsigned short dmaSource_;
unsigned short dmaDestination_;
unsigned char oamDmaPos_;
unsigned char serialCnt_;
bool blanklcd_;
void decEventCycles(IntEventId eventId, unsigned long dec);
void oamDmaInitSetup();
void updateOamDma(unsigned long cycleCounter);
void startOamDma(unsigned long cycleCounter);
void endOamDma(unsigned long cycleCounter);
unsigned char const * oamDmaSrcPtr() const;
unsigned nontrivial_ff_read(unsigned p, unsigned long cycleCounter);
unsigned nontrivial_read(unsigned p, unsigned long cycleCounter);
void nontrivial_ff_write(unsigned p, unsigned data, unsigned long cycleCounter);
void nontrivial_write(unsigned p, unsigned data, unsigned long cycleCounter);
void updateSerial(unsigned long cc);
void updateTimaIrq(unsigned long cc);
void updateIrqs(unsigned long cc);
bool isDoubleSpeed() const { return lcd_.isDoubleSpeed(); }
};
}
#endif
+243
View File
@@ -0,0 +1,243 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "gambatte.h"
#include "cpu.h"
#include "savestate.h"
#include "statesaver.h"
#include "initstate.h"
#include "bootloader.h"
#include <sstream>
#include <cstring>
namespace gambatte {
struct GB::Priv {
CPU cpu;
int stateNo;
bool gbaCgbMode;
Priv() : stateNo(1), gbaCgbMode(false) {}
void full_init(bool clearSram = true);
};
GB::GB() : p_(new Priv) {}
GB::~GB() {
delete p_;
}
long GB::runFor(gambatte::video_pixel_t *const videoBuf, const int pitch,
gambatte::uint_least32_t *const soundBuf, std::size_t soundBufSize, unsigned &samples) {
p_->cpu.setVideoBuffer(videoBuf, pitch);
p_->cpu.setSoundBuffer(soundBuf, soundBufSize);
const long cyclesSinceBlit = p_->cpu.runFor(samples * 2);
samples = p_->cpu.fillSoundBuffer();
return cyclesSinceBlit < 0 ? cyclesSinceBlit : static_cast<long>(samples) - (cyclesSinceBlit >> 1);
}
void GB::Priv::full_init(bool const clearSram) {
SaveState state;
cpu.setStatePtrs(state);
setInitState(state, cpu.isCgb(), gbaCgbMode, clearSram);
cpu.mem_.bootloader.reset();
cpu.mem_.bootloader.set_address_space_start((void*)cpu.rombank0_ptr());
cpu.mem_.bootloader.load(cpu.isCgb(), gbaCgbMode);
if (cpu.mem_.bootloader.using_bootloader) {
uint8_t *ioamhram = (uint8_t*)state.mem.ioamhram.get();
uint8_t serialctrl = (cpu.isCgb() || gbaCgbMode) ? 0x7C : 0x7E;
state.cpu.pc = 0x0000;
// the hw registers must be zeroed out to prevent the logo from being garbled
std::memset((void*)(ioamhram + 0x100), 0x00, 0x100);
//init values taken from SameBoy
ioamhram[0x100] = 0xCF;//joypad initial value
ioamhram[0x102] = serialctrl;//serialctrl
ioamhram[0x148] = 0xFC;//object palette 0
ioamhram[0x149] = 0xFC;//object palette 1
}
cpu.loadState(state);
/* Sachen MMC1 carts need their lock-state overlay installed
* (or their VRAM logo injected) AFTER the generic state
* restore has finished, so the MBC's loadState handler can't
* tear down the overlay based on the default-initialized
* SaveState fields. For every non-Sachen cart this is a
* no-op; for Sachen carts it also wires the read-count
* unlock trigger into the Memory hot path. */
cpu.mem_.sachenLockSetup(cpu.mem_.bootloader.using_bootloader);
}
void GB::reset() {
/* Battery-backed SRAM and RTC must persist across a soft reset
* (matching real-hardware behavior; the cart battery keeps
* volatile SRAM alive while the console is power-cycled). */
p_->full_init(false);
}
void GB::setInputGetter(InputGetter *getInput) {
p_->cpu.setInputGetter(getInput);
}
void GB::setBootloaderGetter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t max_size)) {
p_->cpu.mem_.bootloader.set_bootloader_getter(getter);
}
#ifdef HAVE_NETWORK
void GB::setSerialIO(SerialIO *serial_io) {
p_->cpu.setSerialIO(serial_io);
}
#endif
void *GB::savedata_ptr() { return p_->cpu.savedata_ptr(); }
unsigned GB::savedata_size() { return p_->cpu.savedata_size(); }
void *GB::rtcdata_ptr() { return p_->cpu.rtcdata_ptr(); }
unsigned GB::rtcdata_size() { return p_->cpu.rtcdata_size(); }
int GB::load(const void *romdata, unsigned romsize, const unsigned flags) {
const int failed = p_->cpu.load(romdata, romsize, flags & (FORCE_DMG | FORCE_CGB), flags & MULTICART_COMPAT);
if (!failed) {
p_->gbaCgbMode = flags & GBA_CGB;
p_->full_init();
p_->stateNo = 1;
}
return failed;
}
bool GB::isCgb() const {
return p_->cpu.isCgb();
}
bool GB::isLoaded() const {
/* Previously this unconditionally returned true. The
* underlying Cartridge::loaded() reports whether an MBC has
* been instantiated, which is a faithful "is a ROM loaded"
* signal, so route through to it. */
return p_->cpu.mem_.loaded();
}
void GB::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) {
p_->cpu.setDmgPaletteColor(palNum, colorNum, rgb32);
}
bool GB::loadState(const void *data, size_t size) {
SaveState state;
p_->cpu.setStatePtrs(state);
if (StateSaver::loadState(state, data, size)) {
p_->cpu.loadState(state);
p_->cpu.mem_.bootloader.choosebank(state.mem.ioamhram.get()[0x150] != 0xFF);
return true;
}
return false;
}
void GB::saveState(void *data) {
SaveState state;
p_->cpu.setStatePtrs(state);
p_->cpu.saveState(state);
StateSaver::saveState(state, data);
}
size_t GB::stateSize() const {
SaveState state;
p_->cpu.setStatePtrs(state);
p_->cpu.saveState(state);
return StateSaver::stateSize(state);
}
void GB::setColorCorrection(bool enable) {
p_->cpu.mem_.display_setColorCorrection(enable);
}
void GB::setColorCorrectionMode(unsigned colorCorrectionMode) {
p_->cpu.mem_.display_setColorCorrectionMode(colorCorrectionMode);
}
void GB::setColorCorrectionBrightness(float colorCorrectionBrightness) {
p_->cpu.mem_.display_setColorCorrectionBrightness(colorCorrectionBrightness);
}
void GB::setDarkFilterLevel(unsigned darkFilterLevel) {
p_->cpu.mem_.display_setDarkFilterLevel(darkFilterLevel);
}
video_pixel_t GB::gbcToRgb32(const unsigned bgr15) {
return p_->cpu.mem_.display_gbcToRgb32(bgr15);
}
void GB::setGameGenie(const std::string &codes) {
p_->cpu.setGameGenie(codes);
}
void GB::setGameShark(const std::string &codes) {
p_->cpu.setGameShark(codes);
}
void GB::clearCheats() {
p_->cpu.clearCheats();
}
#ifdef __LIBRETRO__
void *GB::vram_ptr() const {
return p_->cpu.vram_ptr();
}
void *GB::rambank0_ptr() const {
return p_->cpu.rambank0_ptr();
}
void *GB::rambank1_ptr() const {
return p_->cpu.rambank1_ptr();
}
void *GB::rambank2_ptr() const {
return p_->cpu.rambank2_ptr();
}
void *GB::bankedram_ptr() const {
return p_->cpu.bankedram_ptr();
}
void *GB::rombank0_ptr() const {
return p_->cpu.rombank0_ptr();
}
void *GB::rombank1_ptr() const {
return p_->cpu.rombank1_ptr();
}
void *GB::zeropage_ptr() const {
return p_->cpu.zeropage_ptr();
}
void *GB::oamram_ptr() const {
return p_->cpu.oamram_ptr();
}
#endif
}
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aam�s *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef INITSTATE_H
#define INITSTATE_H
namespace gambatte {
/* clearSram defaults to true to preserve the historical behavior
* for callers that load a fresh cart (uninitialized SRAM should
* read as 0xFF). Pass false from a soft-reset path so battery-
* backed cartridge RAM survives the reset, matching real
* hardware behavior. */
void setInitState(struct SaveState &state, bool cgb, bool gbaCgbMode, bool clearSram = true);
}
#endif
+51
View File
@@ -0,0 +1,51 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef INSERTION_SORT_H
#define INSERTION_SORT_H
#include <functional>
template<typename T, class Less>
void insertionSort(T *const start, T *const end, Less less) {
if (start >= end)
return;
T *a = start;
while (++a < end) {
const T e = *a;
T *b = a;
while (b != start && less(e, *(b - 1))) {
*b = *(b - 1);
b = b - 1;
}
*b = e;
}
}
template<typename T>
inline void insertionSort(T *const start, T *const end) {
insertionSort(start, end, std::less<T>());
}
#endif /*INSERTION_SORT_H*/
+85
View File
@@ -0,0 +1,85 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "interrupter.h"
#include "gambatte-memory.h"
namespace gambatte {
Interrupter::Interrupter(unsigned short &sp, unsigned short &pc)
: sp_(sp)
, pc_(pc)
{
}
unsigned long Interrupter::interrupt(unsigned const address, unsigned long cc, Memory &memory) {
cc += 8;
sp_ = (sp_ - 1) & 0xFFFF;
memory.write(sp_, pc_ >> 8, cc);
cc += 4;
sp_ = (sp_ - 1) & 0xFFFF;
memory.write(sp_, pc_ & 0xFF, cc);
pc_ = address;
cc += 8;
if (address == 0x40 && !gsCodes_.empty())
applyVblankCheats(cc, memory);
return cc;
}
static int asHex(char c) {
/* Accept both uppercase and lowercase A-F. The previous
* implementation produced silently-wrong values for
* lowercase letters, leading to no-op cheats when users
* typed codes in lowercase. */
if (c >= 'a' && c <= 'f') return c - 'a' + 0xA;
if (c >= 'A' && c <= 'F') return c - 'A' + 0xA;
return c - '0';
}
void Interrupter::setGameShark(std::string const &codes) {
std::string code;
for (std::size_t pos = 0; pos < codes.length(); pos += code.length() + 1) {
code = codes.substr(pos, codes.find(';', pos) - pos);
if (code.length() >= 8) {
GsCode gs;
gs.type = asHex(code[0]) << 4 | asHex(code[1]);
gs.value = (asHex(code[2]) << 4 | asHex(code[3])) & 0xFF;
gs.address = ( asHex(code[4]) << 4
| asHex(code[5])
| asHex(code[6]) << 12
| asHex(code[7]) << 8) & 0xFFFF;
gsCodes_.push_back(gs);
}
}
}
void Interrupter::clearCheats() {
gsCodes_.clear();
}
void Interrupter::applyVblankCheats(unsigned long const cc, Memory &memory) {
for (std::size_t i = 0, size = gsCodes_.size(); i < size; ++i) {
if (gsCodes_[i].type == 0x01)
memory.write(gsCodes_[i].address, gsCodes_[i].value, cc);
}
}
}
+52
View File
@@ -0,0 +1,52 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef INTERRUPTER_H
#define INTERRUPTER_H
#include <string>
#include <vector>
namespace gambatte {
struct GsCode {
unsigned short address;
unsigned char value;
unsigned char type;
};
class Memory;
class Interrupter {
public:
Interrupter(unsigned short &sp, unsigned short &pc);
unsigned long interrupt(unsigned address, unsigned long cycleCounter, Memory &memory);
void setGameShark(std::string const &codes);
void clearCheats();
private:
unsigned short &sp_;
unsigned short &pc_;
std::vector<GsCode> gsCodes_;
void applyVblankCheats(unsigned long cc, Memory &mem);
};
}
#endif
@@ -0,0 +1,117 @@
//
// Copyright (C) 2010 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "interruptrequester.h"
#include "savestate.h"
namespace gambatte {
InterruptRequester::InterruptRequester()
: eventTimes_(disabled_time)
, minIntTime_(0)
, ifreg_(0)
, iereg_(0)
{
}
void InterruptRequester::saveState(SaveState &state) const {
state.mem.minIntTime = minIntTime_;
state.mem.IME = ime();
state.mem.halted = halted();
}
void InterruptRequester::loadState(SaveState const &state) {
minIntTime_ = state.mem.minIntTime;
ifreg_ = state.mem.ioamhram.get()[0x10F];
iereg_ = state.mem.ioamhram.get()[0x1FF] & 0x1F;
intFlags_.set(state.mem.IME, state.mem.halted);
eventTimes_.setValue<intevent_interrupts>(intFlags_.imeOrHalted() && pendingIrqs()
? minIntTime_
: static_cast<unsigned long>(disabled_time));
}
void InterruptRequester::resetCc(unsigned long oldCc, unsigned long newCc) {
minIntTime_ = minIntTime_ < oldCc ? 0 : minIntTime_ - (oldCc - newCc);
if (eventTimes_.value(intevent_interrupts) != disabled_time)
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
}
void InterruptRequester::ei(unsigned long cc) {
intFlags_.setIme();
minIntTime_ = cc + 1;
if (pendingIrqs())
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
}
void InterruptRequester::di() {
intFlags_.unsetIme();
if (!intFlags_.imeOrHalted())
eventTimes_.setValue<intevent_interrupts>(disabled_time);
}
void InterruptRequester::halt() {
intFlags_.setHalted();
if (pendingIrqs())
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
}
void InterruptRequester::unhalt() {
intFlags_.unsetHalted();
if (!intFlags_.imeOrHalted())
eventTimes_.setValue<intevent_interrupts>(disabled_time);
}
void InterruptRequester::flagIrq(unsigned bit) {
ifreg_ |= bit;
if (intFlags_.imeOrHalted() && pendingIrqs())
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
}
void InterruptRequester::ackIrq(unsigned bit) {
ifreg_ ^= bit;
di();
}
void InterruptRequester::setIereg(unsigned iereg) {
iereg_ = iereg & 0x1F;
if (intFlags_.imeOrHalted()) {
eventTimes_.setValue<intevent_interrupts>(pendingIrqs()
? minIntTime_
: static_cast<unsigned long>(disabled_time));
}
}
void InterruptRequester::setIfreg(unsigned ifreg) {
ifreg_ = ifreg;
if (intFlags_.imeOrHalted()) {
eventTimes_.setValue<intevent_interrupts>(pendingIrqs()
? minIntTime_
: static_cast<unsigned long>(disabled_time));
}
}
}
@@ -0,0 +1,97 @@
//
// Copyright (C) 2010 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef INTERRUPT_REQUESTER_H
#define INTERRUPT_REQUESTER_H
#include "counterdef.h"
#include "minkeeper.h"
namespace gambatte {
struct SaveState;
enum IntEventId { intevent_unhalt,
intevent_end,
intevent_blit,
intevent_serial,
intevent_oam,
intevent_dma,
intevent_tima,
intevent_video,
intevent_interrupts, intevent_last = intevent_interrupts };
class InterruptRequester {
public:
InterruptRequester();
void saveState(SaveState &) const;
void loadState(SaveState const &);
void resetCc(unsigned long oldCc, unsigned long newCc);
unsigned ifreg() const { return ifreg_; }
unsigned pendingIrqs() const { return ifreg_ & iereg_; }
bool ime() const { return intFlags_.ime(); }
bool halted() const { return intFlags_.halted(); }
void ei(unsigned long cc);
void di();
void halt();
void unhalt();
void flagIrq(unsigned bit);
void ackIrq(unsigned bit);
void setIereg(unsigned iereg);
void setIfreg(unsigned ifreg);
IntEventId minEventId() const { return static_cast<IntEventId>(eventTimes_.min()); }
unsigned long minEventTime() const { return eventTimes_.minValue(); }
template<IntEventId id> void setEventTime(unsigned long value) { eventTimes_.setValue<id>(value); }
void setEventTime(IntEventId id, unsigned long value) { eventTimes_.setValue(id, value); }
unsigned long eventTime(IntEventId id) const { return eventTimes_.value(id); }
private:
class IntFlags {
public:
IntFlags() : flags_(0) {}
bool ime() const { return flags_ & flag_ime; }
bool halted() const { return flags_ & flag_halted; }
bool imeOrHalted() const { return flags_; }
void setIme() { flags_ |= flag_ime; }
void unsetIme() { flags_ &= ~flag_ime; }
void setHalted() { flags_ |= flag_halted; }
void unsetHalted() { flags_ &= ~flag_halted; }
void set(bool ime, bool halted) { flags_ = halted * flag_halted + ime * flag_ime; }
private:
unsigned char flags_;
enum { flag_ime = 1, flag_halted = 2 };
};
MinKeeper<intevent_last + 1> eventTimes_;
unsigned long minIntTime_;
unsigned ifreg_;
unsigned iereg_;
IntFlags intFlags_;
};
inline void flagHdmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(0); }
inline void flagGdmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(1); }
inline void ackDmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(disabled_time); }
inline bool hdmaReqFlagged(InterruptRequester const &intreq) { return intreq.eventTime(intevent_dma) == 0; }
inline bool gdmaReqFlagged(InterruptRequester const &intreq) { return intreq.eventTime(intevent_dma) == 1; }
}
#endif
File diff suppressed because it is too large Load Diff
+226
View File
@@ -0,0 +1,226 @@
/***************************************************************************
* Copyright (C) 2007-2010 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef CARTRIDGE_H
#define CARTRIDGE_H
#include "memptrs.h"
#include "rtc.h"
#include "huc3.h"
#include "savestate.h"
#include <memory>
#include <string>
#include <vector>
namespace gambatte
{
class Mbc
{
public:
virtual ~Mbc() {}
virtual void romWrite(unsigned P, unsigned data) {};
virtual void saveState(SaveState::Mem &ss) const {};
virtual void loadState(const SaveState::Mem &ss) {};
virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0;
/* Called once when the boot ROM hands control to the cartridge
* (write of 1 to 0xFF50). No MBC currently implements this --
* the Sachen MMC1 now unlocks on read-count, not on FF50 --
* but the hook is kept for any future MBC that needs to
* sync with end-of-bootstrap. Default is a no-op so every
* other MBC is unaffected. */
virtual void bootloaderFinished() {}
/* Sachen MMC1 only: pointer to the mapper's lock-progress
* counter (a single unsigned char shared with Memory) and
* the unlock entry point. Default null/no-op for every
* other MBC. */
virtual unsigned char *lockCounterPtr() { return 0; }
virtual void unlock() {}
};
class Cartridge
{
public:
Cartridge() : isSachen_(false) {}
void setStatePtrs(SaveState &);
void saveState(SaveState &) const;
void loadState(const SaveState &);
bool loaded() const { return mbc.get(); }
const unsigned char * rmem(unsigned area) const
{
return memptrs_.rmem(area);
}
unsigned char * wmem(unsigned area) const
{
return memptrs_.wmem(area);
}
unsigned char * vramdata() const
{
return memptrs_.vramdata();
}
unsigned char * romdata(unsigned area) const
{
return memptrs_.romdata(area);
}
unsigned char * wramdata(unsigned area) const
{
return memptrs_.wramdata(area);
}
const unsigned char * rdisabledRam() const
{
return memptrs_.rdisabledRam();
}
const unsigned char * rsrambankptr() const
{
return memptrs_.rsrambankptr();
}
unsigned char * wsrambankptr() const
{
return memptrs_.wsrambankptr();
}
unsigned char * vrambankptr() const
{
return memptrs_.vrambankptr();
}
OamDmaSrc oamDmaSrc() const
{
return memptrs_.oamDmaSrc();
}
void setVrambank(unsigned bank)
{
memptrs_.setVrambank(bank);
}
void setWrambank(unsigned bank)
{
memptrs_.setWrambank(bank);
}
void setOamDmaSrc(OamDmaSrc oamDmaSrc)
{
memptrs_.setOamDmaSrc(oamDmaSrc);
}
void mbcWrite(unsigned addr, unsigned data) { mbc->romWrite(addr, data); }
bool isCgb() const
{
return gambatte::isCgb(memptrs_);
}
void rtcWrite(unsigned data)
{
rtc_.write(data);
}
unsigned char rtcRead() const
{
return *rtc_.getActive();
}
const std::string saveBasePath() const;
void setSaveDir(const std::string &dir);
int loadROM(const void *romdata, unsigned int romsize, unsigned int forceModel, bool multicartCompat);
void setGameGenie(const std::string &codes);
void clearCheats();
bool isHuC3() const { return huc3_.isHuC3(); }
unsigned char HuC3Read(unsigned p, unsigned long const cc) { return huc3_.read(p, cc); }
void HuC3Write(unsigned p, unsigned data) { huc3_.write(p, data); }
/* Sachen MMC1 (unlicensed mapper used by Sachen 4-in-1 etc.):
* the cartridge header is bit-scrambled and a "locked" boot
* state requires hooks outside the normal MBC write path.
* isSachen() returns true for Sachen MMC1 carts.
* sachenLockSetup(haveBoot) is called once after the bootloader
* has been installed (or skipped) to
* overlay the locked header bytes
* when the bootstrap will run, or to
* inject the Sachen logo into VRAM
* when running without a bootstrap.
* sachenLockCounterPtr() returns a pointer to the mapper's
* "cart reads in 0x0100..0x01FF"
* counter, or null for non-Sachen
* carts. Memory::read bumps it and
* calls onSachenUnlock() when the
* bootstrap finishes its logo
* display pass.
* onSachenUnlock() drops the locked state mid-boot
* so the bootstrap's verify pass
* sees the unscrambled Nintendo
* logo bytes and succeeds.
* onBootloaderFinished() is a no-op for the new Sachen
* implementation (lock now drops
* on read-count, not on 0xFF50)
* but is kept for any future MBC
* that needs the hook.
* For every non-Sachen cart these functions are no-ops. */
bool isSachen() const { return isSachen_; }
void sachenLockSetup(bool bootloaderUsed);
unsigned char *sachenLockCounterPtr();
void onSachenUnlock();
void onBootloaderFinished() { if (mbc.get()) mbc->bootloaderFinished(); }
void *savedata_ptr();
unsigned savedata_size();
// Not endian-safe at all, but hey.
void *rtcdata_ptr();
unsigned rtcdata_size();
private:
struct AddrData
{
unsigned long addr;
unsigned char data;
AddrData(unsigned long addr, unsigned data) : addr(addr), data(data)
{
}
};
MemPtrs memptrs_;
Rtc rtc_;
HuC3Chip huc3_;
/* True only when the loaded ROM was identified as a Sachen
* MMC1 unlicensed cart via the scrambled-logo checksum check
* in loadROM. Used to route bootloader/VRAM hooks. */
bool isSachen_;
#if __cplusplus >= 201103L
std::unique_ptr<Mbc> mbc;
#else
std::auto_ptr<Mbc> mbc;
#endif
std::vector<AddrData> ggUndoList_;
void applyGameGenie(const std::string &code);
};
}
#endif
@@ -0,0 +1,93 @@
#include "cartridge.h"
#include "../savestate.h"
#include <cstring>
#include <fstream>
namespace gambatte
{
static bool hasBattery(unsigned char headerByte0x147)
{
switch (headerByte0x147)
{
case 0x03:
case 0x06:
case 0x09:
case 0x0F:
case 0x10:
case 0x13:
case 0x1B:
case 0x1E:
case 0xFE:
case 0xFF:
return true;
default:
return false;
}
}
static bool hasRtc(unsigned headerByte0x147)
{
switch (headerByte0x147)
{
case 0xFE: // huc3
case 0x0F:
case 0x10:
return true;
default:
return false;
}
}
void *Cartridge::savedata_ptr()
{
/* Sachen MMC1 carts have no SRAM, and the byte at 0x0147 is
* the descrambled cartridge type which has no Nintendo
* meaning anyway -- skip the hasBattery() check entirely so
* we never hand the frontend a bogus save pointer. */
if (isSachen_)
return 0;
// Check ROM header for battery.
if (hasBattery(memptrs_.romdata()[0x147]))
return memptrs_.rambankdata();
return 0;
}
unsigned Cartridge::savedata_size()
{
if (isSachen_)
return 0;
if (hasBattery(memptrs_.romdata()[0x147]))
return memptrs_.rambankdataend() - memptrs_.rambankdata();
return 0;
}
void *Cartridge::rtcdata_ptr()
{
if (isSachen_)
return 0;
if (hasRtc(memptrs_.romdata()[0x147])) {
if (isHuC3()) {
return &huc3_.getBaseTime();
} else {
return &rtc_.getBaseTime();
}
}
return 0;
}
unsigned Cartridge::rtcdata_size()
{
if (isSachen_)
return 0;
if (hasRtc(memptrs_.romdata()[0x147])) {
if (isHuC3()) {
return sizeof(huc3_.getBaseTime());
} else {
return sizeof(rtc_.getBaseTime());
}
}
return 0;
}
}
+189
View File
@@ -0,0 +1,189 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include "huc3.h"
#include "../savestate.h"
#include "gambatte_log.h"
namespace gambatte {
HuC3Chip::HuC3Chip()
: baseTime_(0)
, haltTime_(0)
, dataTime_(0)
, writingTime_(0)
, ramValue_(0)
, shift_(0)
, ramflag_(0)
, modeflag_(HUC3_NONE)
, irBaseCycle_(0)
, enabled_(false)
, halted_(false)
, irReceivingPulse_(false)
{
}
void HuC3Chip::doLatch() {
uint64_t tmp = (halted_ ? haltTime_ : std::time(0)) - baseTime_;
unsigned minute = (tmp / 60) % 1440;
unsigned day = (tmp / 86400) & 0xFFF;
dataTime_ = (day << 12) | minute;
}
void HuC3Chip::saveState(SaveState &state) const {
state.huc3.baseTime = baseTime_;
state.huc3.haltTime = haltTime_;
state.huc3.dataTime = dataTime_;
state.huc3.writingTime = writingTime_;
state.huc3.ramValue = ramValue_;
state.huc3.shift = shift_;
state.huc3.halted = halted_;
state.huc3.modeflag = modeflag_;
state.huc3.irBaseCycle = irBaseCycle_;
state.huc3.irReceivingPulse = irReceivingPulse_;
}
void HuC3Chip::loadState(SaveState const &state) {
baseTime_ = state.huc3.baseTime;
haltTime_ = state.huc3.haltTime;
dataTime_ = state.huc3.dataTime;
ramValue_ = state.huc3.ramValue;
shift_ = state.huc3.shift;
halted_ = state.huc3.halted;
modeflag_ = state.huc3.modeflag;
writingTime_ = state.huc3.writingTime;
irBaseCycle_ = state.huc3.irBaseCycle;
irReceivingPulse_ = state.huc3.irReceivingPulse;
}
unsigned char HuC3Chip::read(unsigned p, unsigned long const cc) {
// should only reach here with ramflag = 0B-0E
if(ramflag_ == 0x0E) {
// INFRARED
if(!irReceivingPulse_) {
irReceivingPulse_ = true;
irBaseCycle_ = cc;
}
unsigned long cyclesSinceStart = cc - irBaseCycle_;
unsigned char modulation = (cyclesSinceStart/105) & 1; // 4194304 Hz CPU, 40000 Hz remote signal
unsigned long timeUs = cyclesSinceStart*36/151; // actually *1000000/4194304
// sony protocol
if(timeUs < 10000) {
// initialization allowance
return 0;
}
else if(timeUs < 10000 + 2400) {
// initial mark
return modulation;
}
else if(timeUs < 10000 + 2400 + 600) {
// initial space
return 0;
}
else {
// send data
timeUs -= 13000;
// write 20 bits (any 20 seem to do)
unsigned int data = 0xFFFFF;
for(unsigned long mask = 1UL << (20-1); mask; mask >>= 1) {
unsigned int markTime = (data & mask) ? 1200 : 600;
if(timeUs < markTime) { return modulation; }
timeUs -= markTime;
if(timeUs < 600) { return 0; }
timeUs -= 600;
}
return 0;
}
}
if(ramflag_ < 0x0B || ramflag_ > 0x0D) {
gambatte_log(RETRO_LOG_ERROR, "<HuC3> error, hit huc3 read with ramflag=%02X\n", ramflag_);
return 0xFF;
}
if(ramflag_ == 0x0D) return 1;
else return ramValue_;
}
void HuC3Chip::write(unsigned p, unsigned data) {
// as above
if(ramflag_ == 0x0B) {
// command
switch(data & 0xF0) {
case 0x10:
// read time
doLatch();
if(modeflag_ == HUC3_READ) {
ramValue_ = (dataTime_ >> shift_) & 0x0F;
shift_ += 4;
if(shift_ > 24) shift_ = 0;
}
break;
case 0x30:
// write time
if(modeflag_ == HUC3_WRITE) {
if(shift_ == 0) writingTime_ = 0;
if(shift_ < 24) {
writingTime_ |= (data & 0x0F) << shift_;
shift_ += 4;
if(shift_ == 24) {
updateTime();
modeflag_ = HUC3_READ;
}
}
}
break;
case 0x40:
// some kind of mode shift
switch(data & 0x0F) {
case 0x0:
// shift reset?
shift_ = 0;
break;
case 0x3:
// write time?
modeflag_ = HUC3_WRITE;
shift_ = 0;
break;
case 0x7:
modeflag_ = HUC3_READ;
shift_ = 0;
break;
// others are unimplemented so far
}
break;
case 0x50:
// ???
break;
case 0x60:
modeflag_ = HUC3_READ; // ???
break;
}
}
// do nothing for 0C/0D yet
}
void HuC3Chip::updateTime() {
unsigned minute = (writingTime_ & 0xFFF) % 1440;
unsigned day = (writingTime_ & 0xFFF000) >> 12;
baseTime_ = std::time(0) - minute*60 - day*86400;
haltTime_ = baseTime_;
}
}
+79
View File
@@ -0,0 +1,79 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef HuC3Chip_H
#define HuC3Chip_H
enum
{
HUC3_READ = 0,
HUC3_WRITE = 1,
HUC3_NONE = 2
};
#include <ctime>
#include <stdint.h>
namespace gambatte {
struct SaveState;
class HuC3Chip {
public:
HuC3Chip();
uint64_t baseTime() const { return baseTime_; }
void setBaseTime(uint64_t baseTime) { baseTime_ = baseTime; }
uint64_t& getBaseTime()
{
return baseTime_;
}
void saveState(SaveState &state) const;
void loadState(SaveState const &state);
void setRamflag(unsigned char ramflag) { ramflag_ = ramflag; irReceivingPulse_ = false; }
bool isHuC3() const { return enabled_; }
void set(bool enabled) {
enabled_ = enabled;
}
unsigned char read(unsigned p, unsigned long const cc);
void write(unsigned p, unsigned data);
private:
uint64_t baseTime_;
uint64_t haltTime_;
unsigned dataTime_;
unsigned writingTime_;
unsigned char ramValue_;
unsigned char shift_;
unsigned char ramflag_;
unsigned char modeflag_;
unsigned long irBaseCycle_;
bool enabled_;
bool halted_;
bool irReceivingPulse_;
void doLatch();
void updateTime();
};
}
#endif
+168
View File
@@ -0,0 +1,168 @@
/***************************************************************************
* Copyright (C) 2007-2010 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "memptrs.h"
#include <algorithm>
#include <cstring>
namespace gambatte
{
MemPtrs::MemPtrs()
: rmem_()
, wmem_()
, romdata_()
, wramdata_()
, vrambankptr_(0)
, rsrambankptr_(0)
, wsrambankptr_(0)
,memchunk_(0)
, rambankdata_(0)
, wramdataend_(0)
, oamDmaSrc_(oam_dma_src_off)
{
}
MemPtrs::~MemPtrs()
{
delete []memchunk_;
}
void MemPtrs::reset(const unsigned rombanks, const unsigned rambanks, const unsigned wrambanks)
{
delete []memchunk_;
memchunk_ = new unsigned char[
0x4000
+ rombanks * 0x4000ul + 0x4000
+ rambanks * 0x2000ul
+ wrambanks * 0x1000ul
+ 0x4000];
romdata_[0] = romdata();
rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000;
wramdata_[0] = rambankdata_ + rambanks * 0x2000ul;
wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul;
std::memset(rdisabledRamw(), 0xFF, 0x2000);
oamDmaSrc_ = oam_dma_src_off;
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000;
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000;
setRombank(1);
setRambank(0, 0);
setVrambank(0);
setWrambank(1);
}
void MemPtrs::setRombank0(const unsigned bank)
{
romdata_[0] = romdata() + bank * 0x4000ul;
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
disconnectOamDmaAreas();
}
void MemPtrs::setRombank(const unsigned bank)
{
romdata_[1] = romdata() + bank * 0x4000ul - 0x4000;
rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1];
disconnectOamDmaAreas();
}
void MemPtrs::setRambank(const unsigned flags, const unsigned rambank)
{
unsigned char *const srambankptr = (flags & RTC_EN)
? 0
: (rambankdata() != rambankdataend()
? rambankdata_ + rambank * 0x2000ul - 0xA000 : wdisabledRam() - 0xA000);
rsrambankptr_ = (flags & READ_EN) && srambankptr != wdisabledRam() - 0xA000 ? srambankptr : rdisabledRamw() - 0xA000;
wsrambankptr_ = (flags & WRITE_EN) ? srambankptr : wdisabledRam() - 0xA000;
rmem_[0xB] = rmem_[0xA] = rsrambankptr_;
wmem_[0xB] = wmem_[0xA] = wsrambankptr_;
disconnectOamDmaAreas();
}
void MemPtrs::setWrambank(const unsigned bank)
{
wramdata_[1] = wramdata_[0] + ((bank & 0x07) ? (bank & 0x07) : 1) * 0x1000;
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000;
disconnectOamDmaAreas();
}
void MemPtrs::setOamDmaSrc(const OamDmaSrc oamDmaSrc)
{
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1];
rmem_[0xB] = rmem_[0xA] = rsrambankptr_;
wmem_[0xB] = wmem_[0xA] = wsrambankptr_;
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000;
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000;
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000;
oamDmaSrc_ = oamDmaSrc;
disconnectOamDmaAreas();
}
void MemPtrs::disconnectOamDmaAreas()
{
if (isCgb(*this))
{
switch (oamDmaSrc_)
{
case oam_dma_src_rom: // fall through
case oam_dma_src_sram:
case oam_dma_src_invalid:
std::fill(rmem_, rmem_ + 8, static_cast<unsigned char *>(0));
rmem_[0xB] = rmem_[0xA] = 0;
wmem_[0xB] = wmem_[0xA] = 0;
break;
case oam_dma_src_vram:
break;
case oam_dma_src_wram:
rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0;
wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0;
break;
case oam_dma_src_off:
break;
}
}
else
{
switch (oamDmaSrc_)
{
case oam_dma_src_rom: // fall through
case oam_dma_src_sram:
case oam_dma_src_wram:
case oam_dma_src_invalid:
std::fill(rmem_, rmem_ + 8, static_cast<unsigned char *>(0));
rmem_[0xB] = rmem_[0xA] = 0;
wmem_[0xB] = wmem_[0xA] = 0;
rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0;
wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0;
break;
case oam_dma_src_vram:
break;
case oam_dma_src_off:
break;
}
}
}
}
+159
View File
@@ -0,0 +1,159 @@
/***************************************************************************
* Copyright (C) 2007-2010 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef MEMPTRS_H
#define MEMPTRS_H
namespace gambatte
{
enum OamDmaSrc { oam_dma_src_rom,
oam_dma_src_sram,
oam_dma_src_vram,
oam_dma_src_wram,
oam_dma_src_invalid,
oam_dma_src_off, };
class MemPtrs
{
public:
enum RamFlag
{
READ_EN = 1,
WRITE_EN = 2,
RTC_EN = 4
};
MemPtrs();
~MemPtrs();
void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks);
const unsigned char * rmem(unsigned area) const
{
return rmem_[area];
}
unsigned char * wmem(unsigned area) const
{
return wmem_[area];
}
unsigned char * vramdata() const
{
return rambankdata_ - 0x4000;
}
unsigned char * vramdataend() const
{
return rambankdata_;
}
unsigned char * romdata() const
{
return memchunk_ + 0x4000;
}
unsigned char * romdata(unsigned area) const
{
return romdata_[area];
}
unsigned char * romdataend() const
{
return rambankdata_ - 0x4000;
}
unsigned char * wramdata(unsigned area) const
{
return wramdata_[area];
}
unsigned char * wramdataend() const
{
return wramdataend_;
}
unsigned char * rambankdata() const
{
return rambankdata_;
}
unsigned char * rambankdataend() const
{
return wramdata_[0];
}
const unsigned char * rdisabledRam() const
{
return rdisabledRamw();
}
const unsigned char * rsrambankptr() const
{
return rsrambankptr_;
}
unsigned char * wsrambankptr() const
{
return wsrambankptr_;
}
unsigned char * vrambankptr() const
{
return vrambankptr_;
}
OamDmaSrc oamDmaSrc() const
{
return oamDmaSrc_;
}
void setRombank0(unsigned bank);
void setRombank(unsigned bank);
void setRambank(unsigned ramFlags, unsigned rambank);
void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; }
void setWrambank(unsigned bank);
void setOamDmaSrc(OamDmaSrc oamDmaSrc);
private:
unsigned char *romdata_[2];
unsigned char *wramdata_[2];
const unsigned char *rmem_[0x10];
unsigned char *wmem_[0x10];
unsigned char *vrambankptr_;
unsigned char *rsrambankptr_;
unsigned char *wsrambankptr_;
unsigned char *memchunk_;
unsigned char *rambankdata_;
unsigned char *wramdataend_;
OamDmaSrc oamDmaSrc_;
MemPtrs(const MemPtrs &);
MemPtrs & operator=(const MemPtrs &);
void disconnectOamDmaAreas();
unsigned char * rdisabledRamw() const { return wramdataend_ ; }
unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; }
};
inline bool isCgb(const MemPtrs &memptrs)
{
return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000;
}
}
#endif
+170
View File
@@ -0,0 +1,170 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "rtc.h"
#include "../savestate.h"
namespace gambatte
{
Rtc::Rtc()
: activeData_(NULL),
activeSet_(NULL),
baseTime_(0),
haltTime_(0),
index_(5),
dataDh_(0),
dataDl_(0),
dataH_(0),
dataM_(0),
dataS_(0),
enabled_(false),
lastLatchData_(false)
{
}
void Rtc::doLatch()
{
uint64_t tmp = ((dataDh_ & 0x40) ? haltTime_ : std::time(0)) - baseTime_;
while (tmp > 0x1FF * 86400)
{
baseTime_ += 0x1FF * 86400;
tmp -= 0x1FF * 86400;
dataDh_ |= 0x80;
}
dataDl_ = (tmp / 86400) & 0xFF;
dataDh_ &= 0xFE;
dataDh_ |= ((tmp / 86400) & 0x100) >> 8;
tmp %= 86400;
dataH_ = tmp / 3600;
tmp %= 3600;
dataM_ = tmp / 60;
tmp %= 60;
dataS_ = tmp;
}
void Rtc::doSwapActive()
{
if (!enabled_ || index_ > 4)
{
activeData_ = NULL;
activeSet_ = NULL;
}
else switch (index_)
{
case 0x00:
activeData_ = &dataS_;
activeSet_ = &Rtc::setS;
break;
case 0x01:
activeData_ = &dataM_;
activeSet_ = &Rtc::setM;
break;
case 0x02:
activeData_ = &dataH_;
activeSet_ = &Rtc::setH;
break;
case 0x03:
activeData_ = &dataDl_;
activeSet_ = &Rtc::setDl;
break;
case 0x04:
activeData_ = &dataDh_;
activeSet_ = &Rtc::setDh;
break;
}
}
void Rtc::saveState(SaveState &state) const
{
state.rtc.baseTime = baseTime_;
state.rtc.haltTime = haltTime_;
state.rtc.dataDh = dataDh_;
state.rtc.dataDl = dataDl_;
state.rtc.dataH = dataH_;
state.rtc.dataM = dataM_;
state.rtc.dataS = dataS_;
state.rtc.lastLatchData = lastLatchData_;
}
void Rtc::loadState(const SaveState &state)
{
baseTime_ = state.rtc.baseTime;
haltTime_ = state.rtc.haltTime;
dataDh_ = state.rtc.dataDh;
dataDl_ = state.rtc.dataDl;
dataH_ = state.rtc.dataH;
dataM_ = state.rtc.dataM;
dataS_ = state.rtc.dataS;
lastLatchData_ = state.rtc.lastLatchData;
doSwapActive();
}
void Rtc::setDh(const unsigned new_dh)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
const uint64_t old_highdays = ((unixtime - baseTime_) / 86400) & 0x100;
baseTime_ += old_highdays * 86400;
baseTime_ -= ((new_dh & 0x1) << 8) * 86400;
if ((dataDh_ ^ new_dh) & 0x40)
{
if (new_dh & 0x40)
haltTime_ = std::time(0);
else
baseTime_ += std::time(0) - haltTime_;
}
}
void Rtc::setDl(const unsigned new_lowdays)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
const uint64_t old_lowdays = ((unixtime - baseTime_) / 86400) & 0xFF;
baseTime_ += old_lowdays * 86400;
baseTime_ -= new_lowdays * 86400;
}
void Rtc::setH(const unsigned new_hours)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
const uint64_t old_hours = ((unixtime - baseTime_) / 3600) % 24;
baseTime_ += old_hours * 3600;
baseTime_ -= new_hours * 3600;
}
void Rtc::setM(const unsigned new_minutes)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
const uint64_t old_minutes = ((unixtime - baseTime_) / 60) % 60;
baseTime_ += old_minutes * 60;
baseTime_ -= new_minutes * 60;
}
void Rtc::setS(const unsigned new_seconds)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
baseTime_ += (unixtime - baseTime_) % 60;
baseTime_ -= new_seconds;
}
}
+104
View File
@@ -0,0 +1,104 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef RTC_H
#define RTC_H
#include <ctime>
#include <stdint.h>
namespace gambatte
{
struct SaveState;
class Rtc
{
public:
Rtc();
const unsigned char* getActive() const
{
return activeData_;
}
uint64_t& getBaseTime()
{
return baseTime_;
}
void setBaseTime(const uint64_t baseTime)
{
baseTime_ = baseTime;
}
void latch(const unsigned data)
{
if (!lastLatchData_ && data == 1)
doLatch();
lastLatchData_ = data;
}
void saveState(SaveState &state) const;
void loadState(const SaveState &state);
void set(const bool enabled, unsigned bank)
{
bank &= 0xF;
bank -= 8;
this->enabled_ = enabled;
this->index_ = bank;
doSwapActive();
}
void write(const unsigned data)
{
(this->*activeSet_)(data);
*activeData_ = data;
}
private:
unsigned char *activeData_;
void (Rtc::*activeSet_)(unsigned);
uint64_t baseTime_;
uint64_t haltTime_;
unsigned char index_;
unsigned char dataDh_;
unsigned char dataDl_;
unsigned char dataH_;
unsigned char dataM_;
unsigned char dataS_;
bool enabled_;
bool lastLatchData_;
void doLatch();
void doSwapActive();
void setDh(unsigned new_dh);
void setDl(unsigned new_lowdays);
void setH(unsigned new_hours);
void setM(unsigned new_minutes);
void setS(unsigned new_seconds);
};
}
#endif
+157
View File
@@ -0,0 +1,157 @@
/***************************************************************************
* Copyright (C) 2009 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef MINKEEPER_H
#define MINKEEPER_H
#include <algorithm>
namespace MinKeeperUtil
{
template<int n> struct CeiledLog2 { enum { RESULT = 1 + CeiledLog2<(n + 1) / 2>::RESULT }; };
template<> struct CeiledLog2<1> { enum { RESULT = 0 }; };
template<int v, int n> struct RoundedDiv2n { enum { RESULT = RoundedDiv2n<(v + 1) / 2, n - 1>::RESULT }; };
template<int v> struct RoundedDiv2n<v,1> { enum { RESULT = v }; };
template<template<int> class T, int n> struct Sum { enum { RESULT = T<n-1>::RESULT + Sum<T, n-1>::RESULT }; };
template<template<int> class T> struct Sum<T,0> { enum { RESULT = 0 }; };
}
// Keeps track of minimum value identified by id as values change.
// Higher ids prioritized (as min value) if values are equal. Can easily be reversed by swapping < for <=.
// Higher ids can be faster to change when the number of ids isn't a power of 2.
// Thus the ones that change more frequently should have higher ids if priority allows it.
template<int ids>
class MinKeeper
{
enum { LEVELS = MinKeeperUtil::CeiledLog2<ids>::RESULT };
template<int l> struct Num { enum { RESULT = MinKeeperUtil::RoundedDiv2n<ids, LEVELS + 1 - l>::RESULT }; };
template<int l> struct Sum { enum { RESULT = MinKeeperUtil::Sum<Num, l>::RESULT }; };
template<int id, int level>
struct UpdateValue {
enum { P = Sum<level-1>::RESULT + id };
enum { C0 = Sum<level>::RESULT + id * 2 };
static void updateValue(MinKeeper<ids> *const s)
{
// GCC 4.3 generates better code with the ternary operator on i386.
s->a[P] = (id * 2 + 1 == Num<level>::RESULT || s->values[s->a[C0]] < s->values[s->a[C0 + 1]]) ? s->a[C0] : s->a[C0 + 1];
UpdateValue<id / 2, level - 1>::updateValue(s);
}
};
template<int id>
struct UpdateValue<id,0>
{
static void updateValue(MinKeeper<ids> *const s)
{
s->minValue_ = s->values[s->a[0]];
}
};
template<int id, int dummy> struct FillLut {
static void fillLut(MinKeeper<ids> *const s)
{
s->updateValueLut[id] = updateValue<id>;
FillLut<id-1,dummy>::fillLut(s);
}
};
template<int dummy> struct FillLut<-1,dummy> {
static void fillLut(MinKeeper<ids> *const)
{
}
};
unsigned long values[ids];
unsigned long minValue_;
void (*updateValueLut[Num<LEVELS-1>::RESULT])(MinKeeper<ids>*const);
int a[Sum<LEVELS>::RESULT];
template<int id> static void updateValue(MinKeeper<ids> *const s);
public:
MinKeeper(unsigned long initValue = 0xFFFFFFFF);
int min() const { return a[0]; }
unsigned long minValue() const { return minValue_; }
template<int id>
void setValue(const unsigned long cnt)
{
values[id] = cnt;
updateValue<id / 2>(this);
}
void setValue(const int id, const unsigned long cnt)
{
values[id] = cnt;
updateValueLut[id >> 1](this);
}
unsigned long value(const int id) const { return values[id]; }
};
template<int ids>
MinKeeper<ids>::MinKeeper(const unsigned long initValue)
{
std::fill(values, values + ids, initValue);
for (int i = 0; i < Num<LEVELS-1>::RESULT; ++i)
{
a[Sum<LEVELS-1>::RESULT + i] = (i * 2 + 1 == ids || values[i * 2] < values[i * 2 + 1]) ? i * 2 : i * 2 + 1;
}
int n = Num<LEVELS-1>::RESULT;
int off = Sum<LEVELS-1>::RESULT;
while (off)
{
const int pn = (n + 1) >> 1;
const int poff = off - pn;
for (int i = 0; i < pn; ++i)
{
a[poff + i] = (i * 2 + 1 == n ||
values[a[off + i * 2]] < values[a[off + i * 2 + 1]]) ?
a[off + i * 2] : a[off + i * 2 + 1];
}
off = poff;
n = pn;
}
minValue_ = values[a[0]];
FillLut<Num<LEVELS-1>::RESULT-1,0>::fillLut(this);
}
template<int ids>
template<int id>
void MinKeeper<ids>::updateValue(MinKeeper<ids> *const s)
{
s->a[Sum<LEVELS-1>::RESULT + id] = (id * 2 + 1 == ids || s->values[id * 2] < s->values[id * 2 + 1]) ? id * 2 : id * 2 + 1;
UpdateValue<id / 2, LEVELS-1>::updateValue(s);
}
#endif
+229
View File
@@ -0,0 +1,229 @@
//
// Copyright (C) 2008 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SAVESTATE_H
#define SAVESTATE_H
#include <stddef.h>
#include <cstddef>
namespace gambatte {
class SaverList;
struct SaveState {
template<typename T>
class Ptr {
public:
Ptr() : ptr(0), size_(0) {}
T* get() const { return ptr; }
std::size_t size() const { return size_; }
void set(T *p, std::size_t size) { ptr = p; size_ = size; }
friend class SaverList;
friend void setInitState(SaveState &, bool, bool, bool);
private:
T *ptr;
std::size_t size_;
};
struct CPU {
unsigned long cycleCounter;
unsigned short pc;
unsigned short sp;
unsigned char a;
unsigned char b;
unsigned char c;
unsigned char d;
unsigned char e;
unsigned char f;
unsigned char h;
unsigned char l;
bool skip;
} cpu;
struct Mem {
Ptr<unsigned char> vram;
Ptr<unsigned char> sram;
Ptr<unsigned char> wram;
Ptr<unsigned char> ioamhram;
unsigned long divLastUpdate;
unsigned long timaLastUpdate;
unsigned long tmatime;
unsigned long nextSerialtime;
unsigned long lastOamDmaUpdate;
unsigned long minIntTime;
unsigned long unhaltTime;
unsigned short rombank;
unsigned short dmaSource;
unsigned short dmaDestination;
unsigned char rambank;
unsigned char oamDmaPos;
unsigned char HuC3RAMflag;
unsigned char sachenOuterMask;
/* Sachen MMC1: count of CPU reads of 0x0100..0x01FF observed
* while the cartridge is in its boot-time locked state.
* Preserved so a savestate captured mid-bootstrap restores
* the cart to exactly the right point in its 48-reads-to-
* unlock sequence; without it, a mid-boot save would resume
* with count=0 and unlock 48 reads too late, breaking the
* bootstrap's verify pass. Unused (and zero) for every
* non-Sachen cartridge. */
unsigned char sachenLockCount;
#ifdef HAVE_NETWORK
unsigned char serialize_value;
bool serialize_is_fastcgb;
#endif
bool IME;
bool halted;
bool enableRam;
bool rambankMode;
bool hdmaTransfer;
} mem;
struct PPU {
Ptr<unsigned char> bgpData;
Ptr<unsigned char> objpData;
//SpriteMapper::OamReader
Ptr<unsigned char> oamReaderBuf;
Ptr<bool> oamReaderSzbuf;
unsigned char dmgPalette[8 * 3];
unsigned long videoCycles;
unsigned long enableDisplayM0Time;
unsigned short lastM0Time;
unsigned short nextM0Irq;
unsigned short tileword;
unsigned short ntileword;
unsigned char spAttribList[10];
unsigned char spByte0List[10];
unsigned char spByte1List[10];
unsigned char winYPos;
unsigned char xpos;
unsigned char endx;
unsigned char reg0;
unsigned char reg1;
unsigned char attrib;
unsigned char nattrib;
unsigned char state;
unsigned char nextSprite;
unsigned char currentSprite;
unsigned char lyc;
unsigned char m0lyc;
unsigned char oldWy;
unsigned char winDrawState;
unsigned char wscx;
bool weMaster;
bool pendingLcdstatIrq;
} ppu;
struct SPU {
struct Duty {
unsigned long nextPosUpdate;
unsigned char nr3;
unsigned char pos;
bool high;
};
struct Env {
unsigned long counter;
unsigned char volume;
};
struct LCounter {
unsigned long counter;
unsigned short lengthCounter;
};
struct {
struct {
unsigned long counter;
unsigned short shadow;
unsigned char nr0;
bool negging;
} sweep;
Duty duty;
Env env;
LCounter lcounter;
unsigned char nr4;
bool master;
} ch1;
struct {
Duty duty;
Env env;
LCounter lcounter;
unsigned char nr4;
bool master;
} ch2;
struct {
Ptr<unsigned char> waveRam;
LCounter lcounter;
unsigned long waveCounter;
unsigned long lastReadTime;
unsigned char nr3;
unsigned char nr4;
unsigned char wavePos;
unsigned char sampleBuf;
bool master;
} ch3;
struct {
struct {
unsigned long counter;
unsigned short reg;
} lfsr;
Env env;
LCounter lcounter;
unsigned char nr4;
bool master;
} ch4;
unsigned long cycleCounter;
} spu;
struct RTC {
unsigned long baseTime;
unsigned long haltTime;
unsigned char dataDh;
unsigned char dataDl;
unsigned char dataH;
unsigned char dataM;
unsigned char dataS;
bool lastLatchData;
} rtc;
struct HuC3 {
unsigned long baseTime;
unsigned long haltTime;
unsigned long dataTime;
unsigned long writingTime;
unsigned long irBaseCycle;
bool halted;
unsigned char shift;
unsigned char ramValue;
unsigned char modeflag;
bool irReceivingPulse;
} huc3;
};
}
#endif
+35
View File
@@ -0,0 +1,35 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SERIAL_IO_H
#define SERIAL_IO_H
namespace gambatte {
class SerialIO
{
public:
virtual ~SerialIO() {};
virtual bool check(unsigned char out, unsigned char& in, bool& fastCgb) = 0;
virtual unsigned char send(unsigned char data, bool fastCgb) = 0;
};
}
#endif
+206
View File
@@ -0,0 +1,206 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "sound.h"
#include "savestate.h"
#include <cstring>
#include <algorithm>
/*
Frame Sequencer
Step Length Ctr Vol Env Sweep
- - - - - - - - - - - - - - - - - - - -
0 Clock - Clock
S 1 - Clock -
2 Clock - -
3 - - -
4 Clock - Clock
5 - - -
6 Clock - -
7 - - -
- - - - - - - - - - - - - - - - - - - -
Rate 256 Hz 64 Hz 128 Hz
S) start step on sound power on.
*/
namespace gambatte
{
PSG::PSG()
: buffer_(0)
, bufferSize_(0)
, bufferPos_(0)
, lastUpdate_(0)
, soVol_(0)
, rsum_(0x8000) // initialize to 0x8000 to prevent borrows from high word, xor away later
, enabled_(false)
{
}
void PSG::init(const bool cgb)
{
ch1_.init(cgb);
ch3_.init(cgb);
}
void PSG::reset()
{
ch1_.reset();
ch2_.reset();
ch3_.reset();
ch4_.reset();
}
void PSG::setStatePtrs(SaveState &state)
{
ch3_.setStatePtrs(state);
}
void PSG::saveState(SaveState &state)
{
ch1_.saveState(state);
ch2_.saveState(state);
ch3_.saveState(state);
ch4_.saveState(state);
}
void PSG::loadState(const SaveState &state)
{
ch1_.loadState(state);
ch2_.loadState(state);
ch3_.loadState(state);
ch4_.loadState(state);
lastUpdate_ = state.cpu.cycleCounter;
setSoVolume(state.mem.ioamhram.get()[0x124]);
mapSo(state.mem.ioamhram.get()[0x125]);
enabled_ = state.mem.ioamhram.get()[0x126] >> 7 & 1;
}
void PSG::accumulateChannels(const unsigned long cycles)
{
uint_least32_t *const buf = buffer_ + bufferPos_;
std::memset(buf, 0, cycles * sizeof(uint_least32_t));
ch1_.update(buf, soVol_, cycles);
ch2_.update(buf, soVol_, cycles);
ch3_.update(buf, soVol_, cycles);
ch4_.update(buf, soVol_, cycles);
}
void PSG::generateSamples(unsigned long const cycleCounter, bool const doubleSpeed)
{
unsigned long cycles = (cycleCounter - lastUpdate_) >> (1 + doubleSpeed);
if (cycles + bufferPos_ > bufferSize_)
cycles = (bufferSize_ > bufferPos_) ? (bufferSize_ - bufferPos_) : 0;
lastUpdate_ += cycles << (1 + doubleSpeed);
if (cycles)
accumulateChannels(cycles);
bufferPos_ += cycles;
}
void PSG::resetCounter(unsigned long newCc, unsigned long oldCc, bool doubleSpeed)
{
generateSamples(oldCc, doubleSpeed);
lastUpdate_ = newCc - (oldCc - lastUpdate_);
}
size_t PSG::fillBuffer()
{
uint_least32_t sum = rsum_;
uint_least32_t *b = buffer_;
unsigned n = bufferPos_;
if (unsigned n2 = n >> 3)
{
n -= n2 << 3;
do
{
sum += b[0];
b[0] = sum ^ 0x8000;
sum += b[1];
b[1] = sum ^ 0x8000;
sum += b[2];
b[2] = sum ^ 0x8000;
sum += b[3];
b[3] = sum ^ 0x8000;
sum += b[4];
b[4] = sum ^ 0x8000;
sum += b[5];
b[5] = sum ^ 0x8000;
sum += b[6];
b[6] = sum ^ 0x8000;
sum += b[7];
b[7] = sum ^ 0x8000;
b += 8;
} while (--n2);
}
while (n--)
{
sum += *b;
/* xor away the initial rsum value of 0x8000 (which prevents
* borrows from the high word) from the low word */
*b++ = sum ^ 0x8000;
}
rsum_ = sum;
return bufferPos_;
}
#ifdef WORDS_BIGENDIAN
static const unsigned long so1Mul = 0x00000001;
static const unsigned long so2Mul = 0x00010000;
#else
static const unsigned long so1Mul = 0x00010000;
static const unsigned long so2Mul = 0x00000001;
#endif
void PSG::setSoVolume(const unsigned nr50)
{
soVol_ = (((nr50 & 0x7) + 1) * so1Mul
+ ((nr50 >> 4 & 0x7) + 1) * so2Mul) * 64;
}
void PSG::mapSo(const unsigned nr51)
{
const unsigned long tmp = nr51 * so1Mul + (nr51 >> 4) * so2Mul;
ch1_.setSo((tmp & 0x00010001) * 0xFFFF);
ch2_.setSo((tmp >> 1 & 0x00010001) * 0xFFFF);
ch3_.setSo((tmp >> 2 & 0x00010001) * 0xFFFF);
ch4_.setSo((tmp >> 3 & 0x00010001) * 0xFFFF);
}
unsigned PSG::getStatus() const
{
return ch1_.isActive()
| ch2_.isActive() << 1
| ch3_.isActive() << 2
| ch4_.isActive() << 3;
}
}
+95
View File
@@ -0,0 +1,95 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_H
#define SOUND_H
#include <stddef.h>
#include <cstddef>
#include "sound/channel1.h"
#include "sound/channel2.h"
#include "sound/channel3.h"
#include "sound/channel4.h"
namespace gambatte {
class PSG {
public:
PSG();
void init(bool cgb);
void reset();
void setStatePtrs(SaveState &state);
void saveState(SaveState &state);
void loadState(SaveState const &state);
void generateSamples(unsigned long cycleCounter, bool doubleSpeed);
void resetCounter(unsigned long newCc, unsigned long oldCc, bool doubleSpeed);
std::size_t fillBuffer();
void setBuffer(uint_least32_t *buf, std::size_t size) { buffer_ = buf; bufferSize_ = size; bufferPos_ = 0; }
bool isEnabled() const { return enabled_; }
void setEnabled(bool value) { enabled_ = value; }
void setNr10(unsigned data) { ch1_.setNr0(data); }
void setNr11(unsigned data) { ch1_.setNr1(data); }
void setNr12(unsigned data) { ch1_.setNr2(data); }
void setNr13(unsigned data) { ch1_.setNr3(data); }
void setNr14(unsigned data) { ch1_.setNr4(data); }
void setNr21(unsigned data) { ch2_.setNr1(data); }
void setNr22(unsigned data) { ch2_.setNr2(data); }
void setNr23(unsigned data) { ch2_.setNr3(data); }
void setNr24(unsigned data) { ch2_.setNr4(data); }
void setNr30(unsigned data) { ch3_.setNr0(data); }
void setNr31(unsigned data) { ch3_.setNr1(data); }
void setNr32(unsigned data) { ch3_.setNr2(data); }
void setNr33(unsigned data) { ch3_.setNr3(data); }
void setNr34(unsigned data) { ch3_.setNr4(data); }
unsigned waveRamRead(unsigned index) const { return ch3_.waveRamRead(index); }
void waveRamWrite(unsigned index, unsigned data) { ch3_.waveRamWrite(index, data); }
void setNr41(unsigned data) { ch4_.setNr1(data); }
void setNr42(unsigned data) { ch4_.setNr2(data); }
void setNr43(unsigned data) { ch4_.setNr3(data); }
void setNr44(unsigned data) { ch4_.setNr4(data); }
void setSoVolume(unsigned nr50);
void mapSo(unsigned nr51);
unsigned getStatus() const;
private:
Channel1 ch1_;
Channel2 ch2_;
Channel3 ch3_;
Channel4 ch4_;
uint_least32_t *buffer_;
std::size_t bufferSize_;
std::size_t bufferPos_;
unsigned long lastUpdate_;
unsigned long soVol_;
uint_least32_t rsum_;
bool enabled_;
void accumulateChannels(unsigned long cycles);
};
}
#endif
+262
View File
@@ -0,0 +1,262 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "channel1.h"
#include "../savestate.h"
#include <algorithm>
namespace gambatte {
Channel1::SweepUnit::SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit)
: disableMaster_(disabler)
, dutyUnit_(dutyUnit)
, shadow_(0)
, nr0_(0)
, negging_(false)
, cgb_(false)
{
}
unsigned Channel1::SweepUnit::calcFreq() {
unsigned freq = shadow_ >> (nr0_ & 0x07);
if (nr0_ & 0x08) {
freq = shadow_ - freq;
negging_ = true;
} else
freq = shadow_ + freq;
if (freq & 2048)
disableMaster_();
return freq;
}
void Channel1::SweepUnit::event() {
unsigned long const period = nr0_ >> 4 & 0x07;
if (period) {
unsigned const freq = calcFreq();
if (!(freq & 2048) && (nr0_ & 0x07)) {
shadow_ = freq;
dutyUnit_.setFreq(freq, counter_);
calcFreq();
}
counter_ += period << 14;
} else
counter_ += 8ul << 14;
}
void Channel1::SweepUnit::nr0Change(unsigned newNr0) {
if (negging_ && !(newNr0 & 0x08))
disableMaster_();
nr0_ = newNr0;
}
void Channel1::SweepUnit::nr4Init(unsigned long const cc) {
negging_ = false;
shadow_ = dutyUnit_.freq();
unsigned const period = nr0_ >> 4 & 0x07;
unsigned const shift = nr0_ & 0x07;
if (period | shift)
counter_ = ((((cc + 2 + cgb_ * 2) >> 14) + (period ? period : 8)) << 14) + 2;
else
counter_ = counter_disabled;
if (shift)
calcFreq();
}
void Channel1::SweepUnit::reset() {
counter_ = counter_disabled;
}
void Channel1::SweepUnit::saveState(SaveState &state) const {
state.spu.ch1.sweep.counter = counter_;
state.spu.ch1.sweep.shadow = shadow_;
state.spu.ch1.sweep.nr0 = nr0_;
state.spu.ch1.sweep.negging = negging_;
}
void Channel1::SweepUnit::loadState(SaveState const &state) {
counter_ = std::max(state.spu.ch1.sweep.counter, state.spu.cycleCounter);
shadow_ = state.spu.ch1.sweep.shadow;
nr0_ = state.spu.ch1.sweep.nr0;
negging_ = state.spu.ch1.sweep.negging;
}
Channel1::Channel1()
: staticOutputTest_(*this, dutyUnit_)
, disableMaster_(master_, dutyUnit_)
, lengthCounter_(disableMaster_, 0x3F)
, envelopeUnit_(staticOutputTest_)
, sweepUnit_(disableMaster_, dutyUnit_)
, nextEventUnit_(0)
, cycleCounter_(0)
, soMask_(0)
, prevOut_(0)
, nr4_(0)
, master_(false)
{
setEvent();
}
void Channel1::setEvent() {
nextEventUnit_ = &sweepUnit_;
if (envelopeUnit_.counter() < nextEventUnit_->counter())
nextEventUnit_ = &envelopeUnit_;
if (lengthCounter_.counter() < nextEventUnit_->counter())
nextEventUnit_ = &lengthCounter_;
}
void Channel1::setNr0(unsigned data) {
sweepUnit_.nr0Change(data);
setEvent();
}
void Channel1::setNr1(unsigned data) {
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
dutyUnit_.nr1Change(data, cycleCounter_);
setEvent();
}
void Channel1::setNr2(unsigned data) {
if (envelopeUnit_.nr2Change(data))
disableMaster_();
else
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel1::setNr3(unsigned data) {
dutyUnit_.nr3Change(data, cycleCounter_);
setEvent();
}
void Channel1::setNr4(unsigned const data) {
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
nr4_ = data;
dutyUnit_.nr4Change(data, cycleCounter_);
if (data & 0x80) { // init-bit
nr4_ &= 0x7F;
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
sweepUnit_.nr4Init(cycleCounter_);
staticOutputTest_(cycleCounter_);
}
setEvent();
}
void Channel1::setSo(unsigned long soMask) {
soMask_ = soMask;
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel1::reset() {
// cycleCounter >> 12 & 7 represents the frame sequencer position.
cycleCounter_ &= 0xFFF;
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
dutyUnit_.reset();
envelopeUnit_.reset();
sweepUnit_.reset();
setEvent();
}
void Channel1::init(bool cgb) {
sweepUnit_.init(cgb);
}
void Channel1::saveState(SaveState &state) {
sweepUnit_.saveState(state);
dutyUnit_.saveState(state.spu.ch1.duty, cycleCounter_);
envelopeUnit_.saveState(state.spu.ch1.env);
lengthCounter_.saveState(state.spu.ch1.lcounter);
state.spu.cycleCounter = cycleCounter_;
state.spu.ch1.nr4 = nr4_;
state.spu.ch1.master = master_;
}
void Channel1::loadState(SaveState const &state) {
sweepUnit_.loadState(state);
dutyUnit_.loadState(state.spu.ch1.duty, state.mem.ioamhram.get()[0x111],
state.spu.ch1.nr4, state.spu.cycleCounter);
envelopeUnit_.loadState(state.spu.ch1.env, state.mem.ioamhram.get()[0x112],
state.spu.cycleCounter);
lengthCounter_.loadState(state.spu.ch1.lcounter, state.spu.cycleCounter);
cycleCounter_ = state.spu.cycleCounter;
nr4_ = state.spu.ch1.nr4;
master_ = state.spu.ch1.master;
}
void Channel1::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
unsigned long const outLow = outBase * (0 - 15ul);
unsigned long const endCycles = cycleCounter_ + cycles;
for (;;) {
unsigned long const outHigh = master_
? outBase * (envelopeUnit_.getVolume() * 2 - 15ul)
: outLow;
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles);
unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow;
while (dutyUnit_.counter() <= nextMajorEvent) {
*buf = out - prevOut_;
prevOut_ = out;
buf += dutyUnit_.counter() - cycleCounter_;
cycleCounter_ = dutyUnit_.counter();
dutyUnit_.event();
out = dutyUnit_.isHighState() ? outHigh : outLow;
}
if (cycleCounter_ < nextMajorEvent) {
*buf = out - prevOut_;
prevOut_ = out;
buf += nextMajorEvent - cycleCounter_;
cycleCounter_ = nextMajorEvent;
}
if (nextEventUnit_->counter() == nextMajorEvent) {
nextEventUnit_->event();
setEvent();
} else
break;
}
if (cycleCounter_ >= SoundUnit::counter_max) {
dutyUnit_.resetCounters(cycleCounter_);
lengthCounter_.resetCounters(cycleCounter_);
envelopeUnit_.resetCounters(cycleCounter_);
sweepUnit_.resetCounters(cycleCounter_);
cycleCounter_ -= SoundUnit::counter_max;
}
}
}
+92
View File
@@ -0,0 +1,92 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_CHANNEL1_H
#define SOUND_CHANNEL1_H
#include "duty_unit.h"
#include "envelope_unit.h"
#include "gbint.h"
#include "length_counter.h"
#include "master_disabler.h"
#include "static_output_tester.h"
namespace gambatte {
struct SaveState;
class Channel1 {
public:
Channel1();
void setNr0(unsigned data);
void setNr1(unsigned data);
void setNr2(unsigned data);
void setNr3(unsigned data);
void setNr4(unsigned data);
void setSo(unsigned long soMask);
bool isActive() const { return master_; }
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
void reset();
void init(bool cgb);
void saveState(SaveState &state);
void loadState(SaveState const &state);
private:
class SweepUnit : public SoundUnit {
public:
SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit);
virtual void event();
void nr0Change(unsigned newNr0);
void nr4Init(unsigned long cycleCounter);
void reset();
void init(bool cgb) { cgb_ = cgb; }
void saveState(SaveState &state) const;
void loadState(SaveState const &state);
private:
MasterDisabler &disableMaster_;
DutyUnit &dutyUnit_;
unsigned short shadow_;
unsigned char nr0_;
bool negging_;
bool cgb_;
unsigned calcFreq();
};
friend class StaticOutputTester<Channel1, DutyUnit>;
StaticOutputTester<Channel1, DutyUnit> staticOutputTest_;
DutyMasterDisabler disableMaster_;
LengthCounter lengthCounter_;
DutyUnit dutyUnit_;
EnvelopeUnit envelopeUnit_;
SweepUnit sweepUnit_;
SoundUnit *nextEventUnit_;
unsigned long cycleCounter_;
unsigned long soMask_;
unsigned long prevOut_;
unsigned char nr4_;
bool master_;
void setEvent();
};
}
#endif
+160
View File
@@ -0,0 +1,160 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "channel2.h"
#include "../savestate.h"
#include <algorithm>
namespace gambatte {
Channel2::Channel2()
: staticOutputTest_(*this, dutyUnit_)
, disableMaster_(master_, dutyUnit_)
, lengthCounter_(disableMaster_, 0x3F)
, envelopeUnit_(staticOutputTest_)
, cycleCounter_(0)
, soMask_(0)
, prevOut_(0)
, nr4_(0)
, master_(false)
{
setEvent();
}
void Channel2::setEvent() {
nextEventUnit = &envelopeUnit_;
if (lengthCounter_.counter() < nextEventUnit->counter())
nextEventUnit = &lengthCounter_;
}
void Channel2::setNr1(unsigned data) {
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
dutyUnit_.nr1Change(data, cycleCounter_);
setEvent();
}
void Channel2::setNr2(unsigned data) {
if (envelopeUnit_.nr2Change(data))
disableMaster_();
else
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel2::setNr3(unsigned data) {
dutyUnit_.nr3Change(data, cycleCounter_);
setEvent();
}
void Channel2::setNr4(unsigned const data) {
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
nr4_ = data;
if (data & 0x80) { // init-bit
nr4_ &= 0x7F;
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
staticOutputTest_(cycleCounter_);
}
dutyUnit_.nr4Change(data, cycleCounter_);
setEvent();
}
void Channel2::setSo(unsigned long soMask) {
soMask_ = soMask;
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel2::reset() {
// cycleCounter >> 12 & 7 represents the frame sequencer position.
cycleCounter_ &= 0xFFF;
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
dutyUnit_.reset();
envelopeUnit_.reset();
setEvent();
}
void Channel2::saveState(SaveState &state) {
dutyUnit_.saveState(state.spu.ch2.duty, cycleCounter_);
envelopeUnit_.saveState(state.spu.ch2.env);
lengthCounter_.saveState(state.spu.ch2.lcounter);
state.spu.ch2.nr4 = nr4_;
state.spu.ch2.master = master_;
}
void Channel2::loadState(SaveState const &state) {
dutyUnit_.loadState(state.spu.ch2.duty, state.mem.ioamhram.get()[0x116],
state.spu.ch2.nr4, state.spu.cycleCounter);
envelopeUnit_.loadState(state.spu.ch2.env, state.mem.ioamhram.get()[0x117],
state.spu.cycleCounter);
lengthCounter_.loadState(state.spu.ch2.lcounter, state.spu.cycleCounter);
cycleCounter_ = state.spu.cycleCounter;
nr4_ = state.spu.ch2.nr4;
master_ = state.spu.ch2.master;
}
void Channel2::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
unsigned long const outLow = outBase * (0 - 15ul);
unsigned long const endCycles = cycleCounter_ + cycles;
for (;;) {
unsigned long const outHigh = master_
? outBase * (envelopeUnit_.getVolume() * 2 - 15ul)
: outLow;
unsigned long const nextMajorEvent = std::min(nextEventUnit->counter(), endCycles);
unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow;
while (dutyUnit_.counter() <= nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += dutyUnit_.counter() - cycleCounter_;
cycleCounter_ = dutyUnit_.counter();
dutyUnit_.event();
out = dutyUnit_.isHighState() ? outHigh : outLow;
}
if (cycleCounter_ < nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += nextMajorEvent - cycleCounter_;
cycleCounter_ = nextMajorEvent;
}
if (nextEventUnit->counter() == nextMajorEvent) {
nextEventUnit->event();
setEvent();
} else
break;
}
if (cycleCounter_ >= SoundUnit::counter_max) {
dutyUnit_.resetCounters(cycleCounter_);
lengthCounter_.resetCounters(cycleCounter_);
envelopeUnit_.resetCounters(cycleCounter_);
cycleCounter_ -= SoundUnit::counter_max;
}
}
}
+66
View File
@@ -0,0 +1,66 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_CHANNEL2_H
#define SOUND_CHANNEL2_H
#include "duty_unit.h"
#include "envelope_unit.h"
#include "gbint.h"
#include "length_counter.h"
#include "static_output_tester.h"
namespace gambatte {
struct SaveState;
class Channel2 {
public:
Channel2();
void setNr1(unsigned data);
void setNr2(unsigned data);
void setNr3(unsigned data);
void setNr4(unsigned data);
void setSo(unsigned long soMask);
bool isActive() const { return master_; }
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
void reset();
void saveState(SaveState &state);
void loadState(SaveState const &state);
private:
friend class StaticOutputTester<Channel2, DutyUnit>;
StaticOutputTester<Channel2, DutyUnit> staticOutputTest_;
DutyMasterDisabler disableMaster_;
LengthCounter lengthCounter_;
DutyUnit dutyUnit_;
EnvelopeUnit envelopeUnit_;
SoundUnit *nextEventUnit;
unsigned long cycleCounter_;
unsigned long soMask_;
unsigned long prevOut_;
unsigned char nr4_;
bool master_;
void setEvent();
};
}
#endif
+211
View File
@@ -0,0 +1,211 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "channel3.h"
#include "../savestate.h"
#include <algorithm>
#include <cstring>
static inline unsigned toPeriod(unsigned nr3, unsigned nr4) {
return 0x800 - ((nr4 << 8 & 0x700) | nr3);
}
namespace gambatte {
Channel3::Channel3()
: disableMaster_(master_, waveCounter_)
, lengthCounter_(disableMaster_, 0xFF)
, cycleCounter_(0)
, soMask_(0)
, prevOut_(0)
, waveCounter_(SoundUnit::counter_disabled)
, lastReadTime_(0)
, nr0_(0)
, nr3_(0)
, nr4_(0)
, wavePos_(0)
, rshift_(4)
, sampleBuf_(0)
, master_(false)
, cgb_(false)
{
}
void Channel3::setNr0(unsigned data) {
nr0_ = data & 0x80;
if (!(data & 0x80))
disableMaster_();
}
void Channel3::setNr2(unsigned data) {
rshift_ = (data >> 5 & 3U) - 1;
if (rshift_ > 3)
rshift_ = 4;
}
void Channel3::setNr4(unsigned const data) {
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
nr4_ = data & 0x7F;
if (data & nr0_/* & 0x80*/) {
if (!cgb_ && waveCounter_ == cycleCounter_ + 1) {
unsigned const pos = ((wavePos_ + 1) & 0x1F) >> 1;
if (pos < 4)
waveRam_[0] = waveRam_[pos];
else
std::memcpy(waveRam_, waveRam_ + (pos & ~3), 4);
}
master_ = true;
wavePos_ = 0;
lastReadTime_ = waveCounter_ = cycleCounter_ + toPeriod(nr3_, data) + 3;
}
}
void Channel3::setSo(unsigned long soMask) {
soMask_ = soMask;
}
void Channel3::reset() {
// cycleCounter >> 12 & 7 represents the frame sequencer position.
cycleCounter_ &= 0xFFF;
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
sampleBuf_ = 0;
}
void Channel3::init(bool cgb) {
cgb_ = cgb;
}
void Channel3::setStatePtrs(SaveState &state) {
state.spu.ch3.waveRam.set(waveRam_, sizeof waveRam_);
}
void Channel3::saveState(SaveState &state) const {
lengthCounter_.saveState(state.spu.ch3.lcounter);
state.spu.ch3.waveCounter = waveCounter_;
state.spu.ch3.lastReadTime = lastReadTime_;
state.spu.ch3.nr3 = nr3_;
state.spu.ch3.nr4 = nr4_;
state.spu.ch3.wavePos = wavePos_;
state.spu.ch3.sampleBuf = sampleBuf_;
state.spu.ch3.master = master_;
}
void Channel3::loadState(SaveState const &state) {
lengthCounter_.loadState(state.spu.ch3.lcounter, state.spu.cycleCounter);
cycleCounter_ = state.spu.cycleCounter;
waveCounter_ = std::max(state.spu.ch3.waveCounter, state.spu.cycleCounter);
lastReadTime_ = state.spu.ch3.lastReadTime;
nr3_ = state.spu.ch3.nr3;
nr4_ = state.spu.ch3.nr4;
wavePos_ = state.spu.ch3.wavePos & 0x1F;
sampleBuf_ = state.spu.ch3.sampleBuf;
master_ = state.spu.ch3.master;
nr0_ = state.mem.ioamhram.get()[0x11A] & 0x80;
setNr2(state.mem.ioamhram.get()[0x11C]);
}
void Channel3::updateWaveCounter(unsigned long const cc) {
if (cc >= waveCounter_) {
unsigned const period = toPeriod(nr3_, nr4_);
unsigned long const periods = (cc - waveCounter_) / period;
lastReadTime_ = waveCounter_ + periods * period;
waveCounter_ = lastReadTime_ + period;
wavePos_ += periods + 1;
wavePos_ &= 0x1F;
sampleBuf_ = waveRam_[wavePos_ >> 1];
}
}
void Channel3::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
unsigned long const outBase = nr0_/* & 0x80*/ ? soBaseVol & soMask_ : 0;
if (outBase && rshift_ != 4) {
unsigned long const endCycles = cycleCounter_ + cycles;
for (;;) {
unsigned long const nextMajorEvent =
std::min(lengthCounter_.counter(), endCycles);
unsigned long out = master_
? ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul
: 0 - 15ul;
out *= outBase;
while (waveCounter_ <= nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += waveCounter_ - cycleCounter_;
cycleCounter_ = waveCounter_;
lastReadTime_ = waveCounter_;
waveCounter_ += toPeriod(nr3_, nr4_);
++wavePos_;
wavePos_ &= 0x1F;
sampleBuf_ = waveRam_[wavePos_ >> 1];
out = ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul;
out *= outBase;
}
if (cycleCounter_ < nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += nextMajorEvent - cycleCounter_;
cycleCounter_ = nextMajorEvent;
}
if (lengthCounter_.counter() == nextMajorEvent) {
lengthCounter_.event();
} else
break;
}
} else {
unsigned long const out = outBase * (0 - 15ul);
*buf += out - prevOut_;
prevOut_ = out;
cycleCounter_ += cycles;
while (lengthCounter_.counter() <= cycleCounter_) {
updateWaveCounter(lengthCounter_.counter());
lengthCounter_.event();
}
updateWaveCounter(cycleCounter_);
}
if (cycleCounter_ >= SoundUnit::counter_max) {
lengthCounter_.resetCounters(cycleCounter_);
if (waveCounter_ != SoundUnit::counter_disabled)
waveCounter_ -= SoundUnit::counter_max;
lastReadTime_ -= SoundUnit::counter_max;
cycleCounter_ -= SoundUnit::counter_max;
}
}
}
+105
View File
@@ -0,0 +1,105 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_CHANNEL3_H
#define SOUND_CHANNEL3_H
#include "gbint.h"
#include "length_counter.h"
#include "master_disabler.h"
namespace gambatte {
struct SaveState;
class Channel3 {
public:
Channel3();
bool isActive() const { return master_; }
void reset();
void init(bool cgb);
void setStatePtrs(SaveState &state);
void saveState(SaveState &state) const;
void loadState(const SaveState &state);
void setNr0(unsigned data);
void setNr1(unsigned data) { lengthCounter_.nr1Change(data, nr4_, cycleCounter_); }
void setNr2(unsigned data);
void setNr3(unsigned data) { nr3_ = data; }
void setNr4(unsigned data);
void setSo(unsigned long soMask);
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
unsigned waveRamRead(unsigned index) const {
if (master_) {
if (!cgb_ && cycleCounter_ != lastReadTime_)
return 0xFF;
index = wavePos_ >> 1;
}
return waveRam_[index];
}
void waveRamWrite(unsigned index, unsigned data) {
if (master_) {
if (!cgb_ && cycleCounter_ != lastReadTime_)
return;
index = wavePos_ >> 1;
}
waveRam_[index] = data;
}
private:
class Ch3MasterDisabler : public MasterDisabler {
public:
Ch3MasterDisabler(bool &m, unsigned long &wC) : MasterDisabler(m), waveCounter_(wC) {}
virtual void operator()() {
MasterDisabler::operator()();
waveCounter_ = SoundUnit::counter_disabled;
}
private:
unsigned long &waveCounter_;
};
unsigned char waveRam_[0x10];
Ch3MasterDisabler disableMaster_;
LengthCounter lengthCounter_;
unsigned long cycleCounter_;
unsigned long soMask_;
unsigned long prevOut_;
unsigned long waveCounter_;
unsigned long lastReadTime_;
unsigned char nr0_;
unsigned char nr3_;
unsigned char nr4_;
unsigned char wavePos_;
unsigned char rshift_;
unsigned char sampleBuf_;
bool master_;
bool cgb_;
void updateWaveCounter(unsigned long cc);
};
}
#endif
+261
View File
@@ -0,0 +1,261 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "channel4.h"
#include "../savestate.h"
#include <algorithm>
static unsigned long toPeriod(unsigned const nr3) {
unsigned s = (nr3 >> 4) + 3;
unsigned r = nr3 & 7;
if (!r) {
r = 1;
--s;
}
return r << s;
}
namespace gambatte {
Channel4::Lfsr::Lfsr()
: backupCounter_(counter_disabled)
, reg_(0x7FFF)
, nr3_(0)
, master_(false)
{
}
void Channel4::Lfsr::updateBackupCounter(unsigned long const cc) {
if (backupCounter_ <= cc) {
unsigned long const period = toPeriod(nr3_);
unsigned long periods = (cc - backupCounter_) / period + 1;
backupCounter_ += periods * period;
if (master_ && nr3_ < 0xE0) {
if (nr3_ & 8) {
while (periods > 6) {
unsigned const xored = (reg_ << 1 ^ reg_) & 0x7E;
reg_ = (reg_ >> 6 & ~0x7E) | xored | xored << 8;
periods -= 6;
}
unsigned const xored = ((reg_ ^ reg_ >> 1) << (7 - periods)) & 0x7F;
reg_ = (reg_ >> periods & ~(0x80 - (0x80 >> periods))) | xored | xored << 8;
} else {
while (periods > 15) {
reg_ = reg_ ^ reg_ >> 1;
periods -= 15;
}
reg_ = reg_ >> periods | (((reg_ ^ reg_ >> 1) << (15 - periods)) & 0x7FFF);
}
}
}
}
void Channel4::Lfsr::reviveCounter(unsigned long cc) {
updateBackupCounter(cc);
counter_ = backupCounter_;
}
inline void Channel4::Lfsr::event() {
if (nr3_ < 0xE0) {
unsigned const shifted = reg_ >> 1;
unsigned const xored = (reg_ ^ shifted) & 1;
reg_ = shifted | xored << 14;
if (nr3_ & 8)
reg_ = (reg_ & ~0x40) | xored << 6;
}
counter_ += toPeriod(nr3_);
backupCounter_ = counter_;
}
void Channel4::Lfsr::nr3Change(unsigned newNr3, unsigned long cc) {
updateBackupCounter(cc);
nr3_ = newNr3;
counter_ = cc;
}
void Channel4::Lfsr::nr4Init(unsigned long cc) {
disableMaster();
updateBackupCounter(cc);
master_ = true;
backupCounter_ += 4;
counter_ = backupCounter_;
}
void Channel4::Lfsr::reset(unsigned long cc) {
nr3_ = 0;
disableMaster();
backupCounter_ = cc + toPeriod(nr3_);
}
void Channel4::Lfsr::resetCounters(unsigned long oldCc) {
updateBackupCounter(oldCc);
backupCounter_ -= counter_max;
SoundUnit::resetCounters(oldCc);
}
void Channel4::Lfsr::saveState(SaveState &state, unsigned long cc) {
updateBackupCounter(cc);
state.spu.ch4.lfsr.counter = backupCounter_;
state.spu.ch4.lfsr.reg = reg_;
}
void Channel4::Lfsr::loadState(SaveState const &state) {
counter_ = backupCounter_ = std::max(state.spu.ch4.lfsr.counter, state.spu.cycleCounter);
reg_ = state.spu.ch4.lfsr.reg;
master_ = state.spu.ch4.master;
nr3_ = state.mem.ioamhram.get()[0x122];
}
Channel4::Channel4()
: staticOutputTest_(*this, lfsr_)
, disableMaster_(master_, lfsr_)
, lengthCounter_(disableMaster_, 0x3F)
, envelopeUnit_(staticOutputTest_)
, nextEventUnit_(0)
, cycleCounter_(0)
, soMask_(0)
, prevOut_(0)
, nr4_(0)
, master_(false)
{
setEvent();
}
void Channel4::setEvent() {
nextEventUnit_ = &envelopeUnit_;
if (lengthCounter_.counter() < nextEventUnit_->counter())
nextEventUnit_ = &lengthCounter_;
}
void Channel4::setNr1(unsigned data) {
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
setEvent();
}
void Channel4::setNr2(unsigned data) {
if (envelopeUnit_.nr2Change(data))
disableMaster_();
else
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel4::setNr4(unsigned const data) {
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
nr4_ = data;
if (data & 0x80) { // init-bit
nr4_ &= 0x7F;
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
if (master_)
lfsr_.nr4Init(cycleCounter_);
staticOutputTest_(cycleCounter_);
}
setEvent();
}
void Channel4::setSo(unsigned long soMask) {
soMask_ = soMask;
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel4::reset() {
// cycleCounter >> 12 & 7 represents the frame sequencer position.
cycleCounter_ &= 0xFFF;
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
lfsr_.reset(cycleCounter_);
envelopeUnit_.reset();
setEvent();
}
void Channel4::saveState(SaveState &state) {
lfsr_.saveState(state, cycleCounter_);
envelopeUnit_.saveState(state.spu.ch4.env);
lengthCounter_.saveState(state.spu.ch4.lcounter);
state.spu.ch4.nr4 = nr4_;
state.spu.ch4.master = master_;
}
void Channel4::loadState(SaveState const &state) {
lfsr_.loadState(state);
envelopeUnit_.loadState(state.spu.ch4.env, state.mem.ioamhram.get()[0x121],
state.spu.cycleCounter);
lengthCounter_.loadState(state.spu.ch4.lcounter, state.spu.cycleCounter);
cycleCounter_ = state.spu.cycleCounter;
nr4_ = state.spu.ch4.nr4;
master_ = state.spu.ch4.master;
}
void Channel4::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
unsigned long const outLow = outBase * (0 - 15ul);
unsigned long const endCycles = cycleCounter_ + cycles;
for (;;) {
unsigned long const outHigh = outBase * (envelopeUnit_.getVolume() * 2 - 15ul);
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles);
unsigned long out = lfsr_.isHighState() ? outHigh : outLow;
while (lfsr_.counter() <= nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += lfsr_.counter() - cycleCounter_;
cycleCounter_ = lfsr_.counter();
lfsr_.event();
out = lfsr_.isHighState() ? outHigh : outLow;
}
if (cycleCounter_ < nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += nextMajorEvent - cycleCounter_;
cycleCounter_ = nextMajorEvent;
}
if (nextEventUnit_->counter() == nextMajorEvent) {
nextEventUnit_->event();
setEvent();
} else
break;
}
if (cycleCounter_ >= SoundUnit::counter_max) {
lengthCounter_.resetCounters(cycleCounter_);
lfsr_.resetCounters(cycleCounter_);
envelopeUnit_.resetCounters(cycleCounter_);
cycleCounter_ -= SoundUnit::counter_max;
}
}
}
+99
View File
@@ -0,0 +1,99 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_CHANNEL4_H
#define SOUND_CHANNEL4_H
#include "envelope_unit.h"
#include "gbint.h"
#include "length_counter.h"
#include "master_disabler.h"
#include "static_output_tester.h"
namespace gambatte {
struct SaveState;
class Channel4 {
public:
Channel4();
void setNr1(unsigned data);
void setNr2(unsigned data);
void setNr3(unsigned data) { lfsr_.nr3Change(data, cycleCounter_); }
void setNr4(unsigned data);
void setSo(unsigned long soMask);
bool isActive() const { return master_; }
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
void reset();
void saveState(SaveState &state);
void loadState(SaveState const &state);
private:
class Lfsr : public SoundUnit {
public:
Lfsr();
virtual void event();
virtual void resetCounters(unsigned long oldCc);
bool isHighState() const { return ~reg_ & 1; }
void nr3Change(unsigned newNr3, unsigned long cc);
void nr4Init(unsigned long cc);
void reset(unsigned long cc);
void saveState(SaveState &state, unsigned long cc);
void loadState(SaveState const &state);
void disableMaster() { killCounter(); master_ = false; reg_ = 0x7FFF; }
void killCounter() { counter_ = counter_disabled; }
void reviveCounter(unsigned long cc);
private:
unsigned long backupCounter_;
unsigned short reg_;
unsigned char nr3_;
bool master_;
void updateBackupCounter(unsigned long cc);
};
class Ch4MasterDisabler : public MasterDisabler {
public:
Ch4MasterDisabler(bool &m, Lfsr &lfsr) : MasterDisabler(m), lfsr_(lfsr) {}
virtual void operator()() { MasterDisabler::operator()(); lfsr_.disableMaster(); }
private:
Lfsr &lfsr_;
};
friend class StaticOutputTester<Channel4, Lfsr>;
StaticOutputTester<Channel4, Lfsr> staticOutputTest_;
Ch4MasterDisabler disableMaster_;
LengthCounter lengthCounter_;
EnvelopeUnit envelopeUnit_;
Lfsr lfsr_;
SoundUnit *nextEventUnit_;
unsigned long cycleCounter_;
unsigned long soMask_;
unsigned long prevOut_;
unsigned char nr4_;
bool master_;
void setEvent();
};
}
#endif
+158
View File
@@ -0,0 +1,158 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "duty_unit.h"
#include <algorithm>
static inline bool toOutState(unsigned duty, unsigned pos) {
return 0x7EE18180 >> (duty * 8 + pos) & 1;
}
static inline unsigned toPeriod(unsigned freq) {
return (2048 - freq) * 2;
}
namespace gambatte {
DutyUnit::DutyUnit()
: nextPosUpdate_(counter_disabled)
, period_(4096)
, pos_(0)
, duty_(0)
, inc_(0)
, high_(false)
, enableEvents_(true)
{
}
void DutyUnit::updatePos(unsigned long const cc) {
if (cc >= nextPosUpdate_) {
unsigned long const inc = (cc - nextPosUpdate_) / period_ + 1;
nextPosUpdate_ += period_ * inc;
pos_ += inc;
pos_ &= 7;
high_ = toOutState(duty_, pos_);
}
}
void DutyUnit::setCounter() {
static unsigned char const nextStateDistance[4 * 8] = {
7, 6, 5, 4, 3, 2, 1, 1,
1, 6, 5, 4, 3, 2, 1, 2,
1, 4, 3, 2, 1, 4, 3, 2,
1, 6, 5, 4, 3, 2, 1, 2
};
if (enableEvents_ && nextPosUpdate_ != counter_disabled) {
unsigned const npos = (pos_ + 1) & 7;
counter_ = nextPosUpdate_;
inc_ = nextStateDistance[duty_ * 8 + npos];
if (toOutState(duty_, npos) == high_) {
counter_ += period_ * inc_;
inc_ = nextStateDistance[duty_ * 8 + ((npos + inc_) & 7)];
}
} else
counter_ = counter_disabled;
}
void DutyUnit::setFreq(unsigned newFreq, unsigned long cc) {
updatePos(cc);
period_ = toPeriod(newFreq);
setCounter();
}
void DutyUnit::event() {
static unsigned char const inc[] = {
1, 7,
2, 6,
4, 4,
6, 2,
};
high_ ^= true;
counter_ += inc_ * period_;
inc_ = inc[duty_ * 2 + high_];
}
void DutyUnit::nr1Change(unsigned newNr1, unsigned long cc) {
updatePos(cc);
duty_ = newNr1 >> 6;
setCounter();
}
void DutyUnit::nr3Change(unsigned newNr3, unsigned long cc) {
setFreq((freq() & 0x700) | newNr3, cc);
}
void DutyUnit::nr4Change(unsigned const newNr4, unsigned long const cc) {
setFreq((newNr4 << 8 & 0x700) | (freq() & 0xFF), cc);
if (newNr4 & 0x80) {
nextPosUpdate_ = (cc & ~1ul) + period_ + 4;
setCounter();
}
}
void DutyUnit::reset() {
pos_ = 0;
high_ = false;
nextPosUpdate_ = counter_disabled;
setCounter();
}
void DutyUnit::saveState(SaveState::SPU::Duty &dstate, unsigned long const cc) {
updatePos(cc);
setCounter();
dstate.nextPosUpdate = nextPosUpdate_;
dstate.nr3 = freq() & 0xFF;
dstate.pos = pos_;
dstate.high = high_;
}
void DutyUnit::loadState(SaveState::SPU::Duty const &dstate,
unsigned const nr1, unsigned const nr4, unsigned long const cc) {
nextPosUpdate_ = std::max(dstate.nextPosUpdate, cc);
pos_ = dstate.pos & 7;
high_ = dstate.high;
duty_ = nr1 >> 6;
period_ = toPeriod((nr4 << 8 & 0x700) | dstate.nr3);
enableEvents_ = true;
setCounter();
}
void DutyUnit::resetCounters(unsigned long const oldCc) {
if (nextPosUpdate_ == counter_disabled)
return;
updatePos(oldCc);
nextPosUpdate_ -= counter_max;
setCounter();
}
void DutyUnit::killCounter() {
enableEvents_ = false;
setCounter();
}
void DutyUnit::reviveCounter(unsigned long const cc) {
updatePos(cc);
enableEvents_ = true;
setCounter();
}
}
+72
View File
@@ -0,0 +1,72 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef DUTY_UNIT_H
#define DUTY_UNIT_H
#include "sound_unit.h"
#include "master_disabler.h"
#include "../savestate.h"
namespace gambatte {
class DutyUnit : public SoundUnit {
public:
DutyUnit();
virtual void event();
virtual void resetCounters(unsigned long oldCc);
bool isHighState() const { return high_; }
void nr1Change(unsigned newNr1, unsigned long cc);
void nr3Change(unsigned newNr3, unsigned long cc);
void nr4Change(unsigned newNr4, unsigned long cc);
void reset();
void saveState(SaveState::SPU::Duty &dstate, unsigned long cc);
void loadState(SaveState::SPU::Duty const &dstate, unsigned nr1, unsigned nr4, unsigned long cc);
void killCounter();
void reviveCounter(unsigned long cc);
//intended for use by SweepUnit only.
unsigned freq() const { return 2048 - (period_ >> 1); }
void setFreq(unsigned newFreq, unsigned long cc);
private:
unsigned long nextPosUpdate_;
unsigned short period_;
unsigned char pos_;
unsigned char duty_;
unsigned char inc_;
bool high_;
bool enableEvents_;
void setCounter();
void setDuty(unsigned nr1);
void updatePos(unsigned long cc);
};
class DutyMasterDisabler : public MasterDisabler {
public:
DutyMasterDisabler(bool &m, DutyUnit &dutyUnit) : MasterDisabler(m), dutyUnit_(dutyUnit) {}
virtual void operator()() { MasterDisabler::operator()(); dutyUnit_.killCounter(); }
private:
DutyUnit &dutyUnit_;
};
}
#endif
@@ -0,0 +1,96 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "envelope_unit.h"
#include <algorithm>
namespace gambatte {
EnvelopeUnit::VolOnOffEvent EnvelopeUnit::nullEvent_;
EnvelopeUnit::EnvelopeUnit(VolOnOffEvent &volOnOffEvent)
: volOnOffEvent_(volOnOffEvent)
, nr2_(0)
, volume_(0)
{
}
void EnvelopeUnit::reset() {
counter_ = counter_disabled;
}
void EnvelopeUnit::saveState(SaveState::SPU::Env &estate) const {
estate.counter = counter_;
estate.volume = volume_;
}
void EnvelopeUnit::loadState(SaveState::SPU::Env const &estate, unsigned nr2, unsigned long cc) {
counter_ = std::max(estate.counter, cc);
volume_ = estate.volume;
nr2_ = nr2;
}
void EnvelopeUnit::event() {
unsigned long const period = nr2_ & 7;
if (period) {
unsigned newVol = volume_;
if (nr2_ & 8)
++newVol;
else
--newVol;
if (newVol < 0x10U) {
volume_ = newVol;
if (volume_ < 2)
volOnOffEvent_(counter_);
counter_ += period << 15;
} else
counter_ = counter_disabled;
} else
counter_ += 8ul << 15;
}
bool EnvelopeUnit::nr2Change(unsigned const newNr2) {
if (!(nr2_ & 7) && counter_ != counter_disabled)
++volume_;
else if (!(nr2_ & 8))
volume_ += 2;
if ((nr2_ ^ newNr2) & 8)
volume_ = 0x10 - volume_;
volume_ &= 0xF;
nr2_ = newNr2;
return !(newNr2 & 0xF8);
}
bool EnvelopeUnit::nr4Init(unsigned long const cc) {
unsigned long period = (nr2_ & 7) ? nr2_ & 7 : 8;
if (((cc + 2) & 0x7000) == 0x0000)
++period;
counter_ = cc - ((cc - 0x1000) & 0x7FFF) + period * 0x8000;
volume_ = nr2_ >> 4;
return !(nr2_ & 0xF8);
}
}
@@ -0,0 +1,53 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef ENVELOPE_UNIT_H
#define ENVELOPE_UNIT_H
#include "sound_unit.h"
#include "../savestate.h"
namespace gambatte {
class EnvelopeUnit : public SoundUnit {
public:
struct VolOnOffEvent {
virtual ~VolOnOffEvent() {}
virtual void operator()(unsigned long /*cc*/) {}
};
explicit EnvelopeUnit(VolOnOffEvent &volOnOffEvent = nullEvent_);
void event();
bool dacIsOn() const { return nr2_ & 0xF8; }
unsigned getVolume() const { return volume_; }
bool nr2Change(unsigned newNr2);
bool nr4Init(unsigned long cycleCounter);
void reset();
void saveState(SaveState::SPU::Env &estate) const;
void loadState(SaveState::SPU::Env const &estate, unsigned nr2, unsigned long cc);
private:
static VolOnOffEvent nullEvent_;
VolOnOffEvent &volOnOffEvent_;
unsigned char nr2_;
unsigned char volume_;
};
}
#endif
@@ -0,0 +1,82 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "length_counter.h"
#include "master_disabler.h"
#include <algorithm>
namespace gambatte {
LengthCounter::LengthCounter(MasterDisabler &disabler, unsigned const mask)
: disableMaster_(disabler)
, lengthCounter_(0)
, lengthMask_(mask)
{
nr1Change(0, 0, 0);
}
void LengthCounter::event() {
counter_ = counter_disabled;
lengthCounter_ = 0;
disableMaster_();
}
void LengthCounter::nr1Change(unsigned const newNr1, unsigned const nr4, unsigned long const cc) {
lengthCounter_ = (~newNr1 & lengthMask_) + 1;
counter_ = (nr4 & 0x40)
? ((cc >> 13) + lengthCounter_) << 13
: static_cast<unsigned long>(counter_disabled);
}
void LengthCounter::nr4Change(unsigned const oldNr4, unsigned const newNr4, unsigned long const cc) {
if (counter_ != counter_disabled)
lengthCounter_ = (counter_ >> 13) - (cc >> 13);
{
unsigned dec = 0;
if (newNr4 & 0x40) {
dec = ~cc >> 12 & 1;
if (!(oldNr4 & 0x40) && lengthCounter_) {
if (!(lengthCounter_ -= dec))
disableMaster_();
}
}
if ((newNr4 & 0x80) && !lengthCounter_)
lengthCounter_ = lengthMask_ + 1 - dec;
}
if ((newNr4 & 0x40) && lengthCounter_)
counter_ = ((cc >> 13) + lengthCounter_) << 13;
else
counter_ = counter_disabled;
}
void LengthCounter::saveState(SaveState::SPU::LCounter &lstate) const {
lstate.counter = counter_;
lstate.lengthCounter = lengthCounter_;
}
void LengthCounter::loadState(SaveState::SPU::LCounter const &lstate, unsigned long const cc) {
counter_ = std::max(lstate.counter, cc);
lengthCounter_ = lstate.lengthCounter;
}
}
@@ -0,0 +1,46 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef LENGTH_COUNTER_H
#define LENGTH_COUNTER_H
#include "sound_unit.h"
#include "../savestate.h"
namespace gambatte {
class MasterDisabler;
class LengthCounter : public SoundUnit {
public:
LengthCounter(MasterDisabler &disabler, unsigned lengthMask);
virtual void event();
void nr1Change(unsigned newNr1, unsigned nr4, unsigned long cc);
void nr4Change(unsigned oldNr4, unsigned newNr4, unsigned long cc);
void saveState(SaveState::SPU::LCounter &lstate) const;
void loadState(SaveState::SPU::LCounter const &lstate, unsigned long cc);
private:
MasterDisabler &disableMaster_;
unsigned short lengthCounter_;
unsigned char const lengthMask_;
};
}
#endif
@@ -0,0 +1,36 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef MASTER_DISABLER_H
#define MASTER_DISABLER_H
namespace gambatte {
class MasterDisabler {
public:
explicit MasterDisabler(bool &master) : master_(master) {}
virtual ~MasterDisabler() {}
virtual void operator()() { master_ = false; }
private:
bool &master_;
};
}
#endif
+45
View File
@@ -0,0 +1,45 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_UNIT_H
#define SOUND_UNIT_H
namespace gambatte {
class SoundUnit {
public:
enum { counter_max = 0x80000000u, counter_disabled = 0xFFFFFFFFu };
virtual ~SoundUnit() {}
virtual void event() = 0;
virtual void resetCounters(unsigned long /*oldCc*/) {
if (counter_ != counter_disabled)
counter_ -= counter_max;
}
unsigned long counter() const { return counter_; }
protected:
SoundUnit() : counter_(counter_disabled) {}
unsigned long counter_;
};
}
#endif
@@ -0,0 +1,47 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef STATIC_OUTPUT_TESTER_H
#define STATIC_OUTPUT_TESTER_H
#include "envelope_unit.h"
namespace gambatte {
template<class Channel, class Unit>
class StaticOutputTester : public EnvelopeUnit::VolOnOffEvent {
public:
StaticOutputTester(Channel const &ch, Unit &unit) : ch_(ch), unit_(unit) {}
void operator()(unsigned long cc);
private:
Channel const &ch_;
Unit &unit_;
};
template<class Channel, class Unit>
void StaticOutputTester<Channel, Unit>::operator()(unsigned long cc) {
if (ch_.soMask_ && ch_.master_ && ch_.envelopeUnit_.getVolume())
unit_.reviveCounter(cc);
else
unit_.killCounter();
}
}
#endif
+604
View File
@@ -0,0 +1,604 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "statesaver.h"
#include "savestate.h"
#include "gambatte-array.h"
#include <stdint.h>
#include <vector>
#include <cstring>
#include <algorithm>
class omemstream
{
public:
omemstream(void *data) : wr_ptr(static_cast<uint8_t*>(data)), has_written(0) {}
void put(uint8_t data)
{
if (wr_ptr)
*wr_ptr++ = data;
has_written++;
}
void write(const void *data, size_t size)
{
if (wr_ptr)
{
std::memcpy(wr_ptr, data, size);
wr_ptr += size;
}
has_written += size;
}
size_t size() const { return has_written; }
bool fail() const { return false; }
bool good() const { return true; }
private:
uint8_t *wr_ptr;
size_t has_written;
};
class imemstream
{
public:
imemstream(const void *data, size_t size)
: rd_ptr(static_cast<const uint8_t*>(data))
, rd_end(static_cast<const uint8_t*>(data) + size)
, has_read(0)
, failed_(false)
{}
uint8_t get()
{
/* On underflow return 0 and latch failure. Callers
* cannot ignore failure in a way that would dereference
* the read pointer because rd_ptr is never advanced past
* rd_end. Previously this function unconditionally
* dereferenced and incremented rd_ptr, allowing a
* malformed savestate to read arbitrary memory past the
* buffer end. */
if (rd_ptr >= rd_end) {
failed_ = true;
return 0;
}
uint8_t ret = *rd_ptr++;
has_read++;
return ret;
}
void read(void *data, size_t size)
{
size_t avail = (size_t)(rd_end - rd_ptr);
size_t n = size < avail ? size : avail;
if (n)
std::memcpy(data, rd_ptr, n);
/* Pad the tail with zeroes if the buffer is short, to
* keep caller-visible behavior deterministic when a
* truncated state is fed in. */
if (n < size) {
std::memset(static_cast<uint8_t*>(data) + n, 0, size - n);
failed_ = true;
}
rd_ptr += n;
has_read += size;
}
void ignore(size_t len = 1)
{
size_t avail = (size_t)(rd_end - rd_ptr);
size_t n = len < avail ? len : avail;
if (n < len)
failed_ = true;
rd_ptr += n;
has_read += len;
}
void getline(char *data, size_t size, char delim = '\n')
{
size_t count = 0;
while ((count < size - 1) && (rd_ptr < rd_end) && (*rd_ptr != delim))
{
*data++ = *rd_ptr++;
has_read++;
count++;
}
if (rd_ptr < rd_end) {
rd_ptr++;
has_read++;
} else {
failed_ = true;
}
*data = '\0';
}
bool fail() const { return failed_; }
bool good() const { return !failed_ && rd_ptr < rd_end; }
private:
const uint8_t *rd_ptr;
const uint8_t *rd_end;
size_t has_read;
bool failed_;
};
namespace {
using namespace gambatte;
enum AsciiChar {
NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, TAB, LF, VT, FF, CR, SO, SI,
DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FS, GS, RS, US,
SP, XCL, QOT, HSH, DLR, PRC, AMP, APO, LPA, RPA, AST, PLU, COM, HYP, STP, DIV,
NO0, NO1, NO2, NO3, NO4, NO5, NO6, NO7, NO8, NO9, CLN, SCL, LT, EQL, GT, QTN,
AT, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O,
P, Q, R, S, T, U, V, W, X, Y, Z, LBX, BSL, RBX, CAT, UND,
ACN, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o,
p, q, r, s, t, u, v, w, x, y, z, LBR, BAR, RBR, TLD, DEL
};
struct Saver {
const char *label;
void (*save)(omemstream &file, const SaveState &state);
void (*load)(imemstream &file, SaveState &state);
unsigned char labelsize;
};
static inline bool operator<(const Saver &l, const Saver &r) {
return std::strcmp(l.label, r.label) < 0;
}
static void put24(omemstream &file, const unsigned long data) {
file.put(data >> 16 & 0xFF);
file.put(data >> 8 & 0xFF);
file.put(data & 0xFF);
}
static void put32(omemstream &file, const unsigned long data) {
file.put(data >> 24 & 0xFF);
file.put(data >> 16 & 0xFF);
file.put(data >> 8 & 0xFF);
file.put(data & 0xFF);
}
static void write(omemstream &file, const unsigned char data) {
static const char inf[] = { 0x00, 0x00, 0x01 };
file.write(inf, sizeof(inf));
file.put(data & 0xFF);
}
static void write(omemstream &file, const unsigned short data) {
static const char inf[] = { 0x00, 0x00, 0x02 };
file.write(inf, sizeof(inf));
file.put(data >> 8 & 0xFF);
file.put(data & 0xFF);
}
static void write(omemstream &file, const unsigned long data) {
static const char inf[] = { 0x00, 0x00, 0x04 };
file.write(inf, sizeof(inf));
put32(file, data);
}
static inline void write(omemstream &file, const bool data) {
write(file, static_cast<unsigned char>(data));
}
static void write(omemstream &file, const unsigned char *data, const unsigned long sz) {
put24(file, sz);
file.write(reinterpret_cast<const char*>(data), sz);
}
static void write(omemstream &file, const bool *data, const unsigned long sz) {
put24(file, sz);
for (unsigned long i = 0; i < sz; ++i)
file.put(data[i]);
}
static unsigned long get24(imemstream &file) {
unsigned long tmp = file.get() & 0xFF;
tmp = tmp << 8 | (file.get() & 0xFF);
return tmp << 8 | (file.get() & 0xFF);
}
static unsigned long read(imemstream &file) {
unsigned long size = get24(file);
if (size > 4) {
file.ignore(size - 4);
size = 4;
}
unsigned long out = 0;
switch (size) {
case 4: out = (out | (file.get() & 0xFF)) << 8;
case 3: out = (out | (file.get() & 0xFF)) << 8;
case 2: out = (out | (file.get() & 0xFF)) << 8;
case 1: out = out | (file.get() & 0xFF);
}
return out;
}
static inline void read(imemstream &file, unsigned char &data) {
data = read(file) & 0xFF;
}
static inline void read(imemstream &file, unsigned short &data) {
data = read(file) & 0xFFFF;
}
static inline void read(imemstream &file, unsigned long &data) {
data = read(file);
}
static inline void read(imemstream &file, bool &data) {
data = read(file);
}
static void read(imemstream &file, unsigned char *data, unsigned long sz) {
const unsigned long size = get24(file);
if (size < sz)
sz = size;
file.read(reinterpret_cast<char*>(data), sz);
file.ignore(size - sz);
/* Previous code had:
* if (static_cast<unsigned char>(0x100))
* for (...) data[i] &= 0xFF;
* The condition is always false (0x100 truncated to
* unsigned char is 0), so the masking loop was dead.
* Removed. unsigned char already holds 0..0xFF by definition. */
}
static void read(imemstream &file, bool *data, unsigned long sz) {
const unsigned long size = get24(file);
if (size < sz)
sz = size;
for (unsigned long i = 0; i < sz; ++i)
data[i] = file.get();
file.ignore(size - sz);
}
} // anon namespace
namespace gambatte {
class SaverList {
public:
typedef std::vector<Saver> list_t;
typedef list_t::const_iterator const_iterator;
private:
list_t list;
unsigned char maxLabelsize_;
public:
SaverList();
const_iterator begin() const { return list.begin(); }
const_iterator end() const { return list.end(); }
unsigned char maxLabelsize() const { return maxLabelsize_; }
};
static void pushSaver(SaverList::list_t &list, const char *label,
void (*save)(omemstream &file, const SaveState &state),
void (*load)(imemstream &file, SaveState &state), unsigned char labelsize) {
const Saver saver = { label, save, load, labelsize };
list.push_back(saver);
}
SaverList::SaverList() {
#define ADD(arg) do { \
struct Func { \
static void save(omemstream &file, const SaveState &state) { write(file, state.arg); } \
static void load(imemstream &file, SaveState &state) { read(file, state.arg); } \
}; \
\
pushSaver(list, label, Func::save, Func::load, sizeof label); \
} while (0)
#define ADDPTR(arg) do { \
struct Func { \
static void save(omemstream &file, const SaveState &state) { write(file, state.arg.get(), state.arg.size()); } \
static void load(imemstream &file, SaveState &state) { read(file, state.arg.ptr, state.arg.size()); } \
}; \
\
pushSaver(list, label, Func::save, Func::load, sizeof label); \
} while (0)
#define ADDARRAY(arg) do { \
struct Func { \
static void save(omemstream &file, const SaveState &state) { write(file, state.arg, sizeof(state.arg)); } \
static void load(imemstream &file, SaveState &state) { read(file, state.arg, sizeof(state.arg)); } \
}; \
\
pushSaver(list, label, Func::save, Func::load, sizeof label); \
} while (0)
{ static const char label[] = { c,c, NUL }; ADD(cpu.cycleCounter); }
{ static const char label[] = { p,c, NUL }; ADD(cpu.pc); }
{ static const char label[] = { s,p, NUL }; ADD(cpu.sp); }
{ static const char label[] = { a, NUL }; ADD(cpu.a); }
{ static const char label[] = { b, NUL }; ADD(cpu.b); }
{ static const char label[] = { c, NUL }; ADD(cpu.c); }
{ static const char label[] = { d, NUL }; ADD(cpu.d); }
{ static const char label[] = { e, NUL }; ADD(cpu.e); }
{ static const char label[] = { f, NUL }; ADD(cpu.f); }
{ static const char label[] = { h, NUL }; ADD(cpu.h); }
{ static const char label[] = { l, NUL }; ADD(cpu.l); }
{ static const char label[] = { s,k,i,p, NUL }; ADD(cpu.skip); }
{ static const char label[] = { h,a,l,t, NUL }; ADD(mem.halted); }
{ static const char label[] = { v,r,a,m, NUL }; ADDPTR(mem.vram); }
{ static const char label[] = { s,r,a,m, NUL }; ADDPTR(mem.sram); }
{ static const char label[] = { w,r,a,m, NUL }; ADDPTR(mem.wram); }
{ static const char label[] = { h,r,a,m, NUL }; ADDPTR(mem.ioamhram); }
{ static const char label[] = { l,d,i,v,u,p, NUL }; ADD(mem.divLastUpdate); }
{ static const char label[] = { l,t,i,m,a,u,p, NUL }; ADD(mem.timaLastUpdate); }
{ static const char label[] = { t,m,a,t,i,m,e, NUL }; ADD(mem.tmatime); }
{ static const char label[] = { s,e,r,i,a,l,t, NUL }; ADD(mem.nextSerialtime); }
{ static const char label[] = { l,o,d,m,a,u,p, NUL }; ADD(mem.lastOamDmaUpdate); }
{ static const char label[] = { m,i,n,i,n,t,t, NUL }; ADD(mem.minIntTime); }
{ static const char label[] = { u,n,h,a,l,t,t, NUL }; ADD(mem.unhaltTime); }
{ static const char label[] = { r,o,m,b,a,n,k, NUL }; ADD(mem.rombank); }
{ static const char label[] = { d,m,a,s,r,c, NUL }; ADD(mem.dmaSource); }
{ static const char label[] = { d,m,a,d,s,t, NUL }; ADD(mem.dmaDestination); }
{ static const char label[] = { r,a,m,b,a,n,k, NUL }; ADD(mem.rambank); }
{ static const char label[] = { o,d,m,a,p,o,s, NUL }; ADD(mem.oamDmaPos); }
#ifdef HAVE_NETWORK
{ static const char label[] = { n,e,t,s,v, NUL }; ADD(mem.serialize_value); }
{ static const char label[] = { n,e,t,s,f,c, NUL }; ADD(mem.serialize_is_fastcgb); }
#endif
{ static const char label[] = { i,m,e, NUL }; ADD(mem.IME); }
{ static const char label[] = { s,r,a,m,o,n, NUL }; ADD(mem.enableRam); }
{ static const char label[] = { r,a,m,b,m,o,d, NUL }; ADD(mem.rambankMode); }
{ static const char label[] = { h,d,m,a, NUL }; ADD(mem.hdmaTransfer); }
{ static const char label[] = { h,u,c,NO3,r,a,m, NUL }; ADD(mem.HuC3RAMflag); }
{ static const char label[] = { s,c,h,n,o,m,k, NUL }; ADD(mem.sachenOuterMask); }
{ static const char label[] = { s,c,h,n,l,c,t, NUL }; ADD(mem.sachenLockCount); }
{ static const char label[] = { b,g,p, NUL }; ADDPTR(ppu.bgpData); }
{ static const char label[] = { o,b,j,p, NUL }; ADDPTR(ppu.objpData); }
{ static const char label[] = { s,p,o,s,b,u,f, NUL }; ADDPTR(ppu.oamReaderBuf); }
{ static const char label[] = { s,p,s,z,b,u,f, NUL }; ADDPTR(ppu.oamReaderSzbuf); }
{ static const char label[] = { d,m,g,p,a,l, NUL }; ADDARRAY(ppu.dmgPalette); }
{ static const char label[] = { s,p,a,t,t,r, NUL }; ADDARRAY(ppu.spAttribList); }
{ static const char label[] = { s,p,b,y,t,e,NO0, NUL }; ADDARRAY(ppu.spByte0List); }
{ static const char label[] = { s,p,b,y,t,e,NO1, NUL }; ADDARRAY(ppu.spByte1List); }
{ static const char label[] = { v,c,y,c,l,e,s, NUL }; ADD(ppu.videoCycles); }
{ static const char label[] = { e,d,M,NO0,t,i,m, NUL }; ADD(ppu.enableDisplayM0Time); }
{ static const char label[] = { m,NO0,t,i,m,e, NUL }; ADD(ppu.lastM0Time); }
{ static const char label[] = { n,m,NO0,i,r,q, NUL }; ADD(ppu.nextM0Irq); }
{ static const char label[] = { b,g,t,w, NUL }; ADD(ppu.tileword); }
{ static const char label[] = { b,g,n,t,w, NUL }; ADD(ppu.ntileword); }
{ static const char label[] = { w,i,n,y,p,o,s, NUL }; ADD(ppu.winYPos); }
{ static const char label[] = { x,p,o,s, NUL }; ADD(ppu.xpos); }
{ static const char label[] = { e,n,d,x, NUL }; ADD(ppu.endx); }
{ static const char label[] = { p,p,u,r,NO0, NUL }; ADD(ppu.reg0); }
{ static const char label[] = { p,p,u,r,NO1, NUL }; ADD(ppu.reg1); }
{ static const char label[] = { b,g,a,t,r,b, NUL }; ADD(ppu.attrib); }
{ static const char label[] = { b,g,n,a,t,r,b, NUL }; ADD(ppu.nattrib); }
{ static const char label[] = { p,p,u,s,t,a,t, NUL }; ADD(ppu.state); }
{ static const char label[] = { n,s,p,r,i,t,e, NUL }; ADD(ppu.nextSprite); }
{ static const char label[] = { c,s,p,r,i,t,e, NUL }; ADD(ppu.currentSprite); }
{ static const char label[] = { l,y,c, NUL }; ADD(ppu.lyc); }
{ static const char label[] = { m,NO0,l,y,c, NUL }; ADD(ppu.m0lyc); }
{ static const char label[] = { o,l,d,w,y, NUL }; ADD(ppu.oldWy); }
{ static const char label[] = { w,i,n,d,r,a,w, NUL }; ADD(ppu.winDrawState); }
{ static const char label[] = { w,s,c,x, NUL }; ADD(ppu.wscx); }
{ static const char label[] = { w,e,m,a,s,t,r, NUL }; ADD(ppu.weMaster); }
{ static const char label[] = { l,c,d,s,i,r,q, NUL }; ADD(ppu.pendingLcdstatIrq); }
{ static const char label[] = { s,p,u,c,n,t,r, NUL }; ADD(spu.cycleCounter); }
{ static const char label[] = { s,w,p,c,n,t,r, NUL }; ADD(spu.ch1.sweep.counter); }
{ static const char label[] = { s,w,p,s,h,d,w, NUL }; ADD(spu.ch1.sweep.shadow); }
{ static const char label[] = { s,w,p,n,e,g, NUL }; ADD(spu.ch1.sweep.negging); }
{ static const char label[] = { d,u,t,NO1,c,t,r, NUL }; ADD(spu.ch1.duty.nextPosUpdate); }
{ static const char label[] = { d,u,t,NO1,p,o,s, NUL }; ADD(spu.ch1.duty.pos); }
{ static char const label[] = { d,u,t,NO1,h,i, NUL }; ADD(spu.ch1.duty.high); }
{ static const char label[] = { e,n,v,NO1,c,t,r, NUL }; ADD(spu.ch1.env.counter); }
{ static const char label[] = { e,n,v,NO1,v,o,l, NUL }; ADD(spu.ch1.env.volume); }
{ static const char label[] = { l,e,n,NO1,c,t,r, NUL }; ADD(spu.ch1.lcounter.counter); }
{ static const char label[] = { l,e,n,NO1,v,a,l, NUL }; ADD(spu.ch1.lcounter.lengthCounter); }
{ static const char label[] = { n,r,NO1,NO0, NUL }; ADD(spu.ch1.sweep.nr0); }
{ static const char label[] = { n,r,NO1,NO3, NUL }; ADD(spu.ch1.duty.nr3); }
{ static const char label[] = { n,r,NO1,NO4, NUL }; ADD(spu.ch1.nr4); }
{ static const char label[] = { c,NO1,m,a,s,t,r, NUL }; ADD(spu.ch1.master); }
{ static const char label[] = { d,u,t,NO2,c,t,r, NUL }; ADD(spu.ch2.duty.nextPosUpdate); }
{ static const char label[] = { d,u,t,NO2,p,o,s, NUL }; ADD(spu.ch2.duty.pos); }
{ static char const label[] = { d,u,t,NO2,h,i, NUL }; ADD(spu.ch2.duty.high); }
{ static const char label[] = { e,n,v,NO2,c,t,r, NUL }; ADD(spu.ch2.env.counter); }
{ static const char label[] = { e,n,v,NO2,v,o,l, NUL }; ADD(spu.ch2.env.volume); }
{ static const char label[] = { l,e,n,NO2,c,t,r, NUL }; ADD(spu.ch2.lcounter.counter); }
{ static const char label[] = { l,e,n,NO2,v,a,l, NUL }; ADD(spu.ch2.lcounter.lengthCounter); }
{ static const char label[] = { n,r,NO2,NO3, NUL }; ADD(spu.ch2.duty.nr3); }
{ static const char label[] = { n,r,NO2,NO4, NUL }; ADD(spu.ch2.nr4); }
{ static const char label[] = { c,NO2,m,a,s,t,r, NUL }; ADD(spu.ch2.master); }
{ static const char label[] = { w,a,v,e,r,a,m, NUL }; ADDPTR(spu.ch3.waveRam); }
{ static const char label[] = { l,e,n,NO3,c,t,r, NUL }; ADD(spu.ch3.lcounter.counter); }
{ static const char label[] = { l,e,n,NO3,v,a,l, NUL }; ADD(spu.ch3.lcounter.lengthCounter); }
{ static const char label[] = { w,a,v,e,c,t,r, NUL }; ADD(spu.ch3.waveCounter); }
{ static const char label[] = { l,w,a,v,r,d,t, NUL }; ADD(spu.ch3.lastReadTime); }
{ static const char label[] = { w,a,v,e,p,o,s, NUL }; ADD(spu.ch3.wavePos); }
{ static const char label[] = { w,a,v,s,m,p,l, NUL }; ADD(spu.ch3.sampleBuf); }
{ static const char label[] = { n,r,NO3,NO3, NUL }; ADD(spu.ch3.nr3); }
{ static const char label[] = { n,r,NO3,NO4, NUL }; ADD(spu.ch3.nr4); }
{ static const char label[] = { c,NO3,m,a,s,t,r, NUL }; ADD(spu.ch3.master); }
{ static const char label[] = { l,f,s,r,c,t,r, NUL }; ADD(spu.ch4.lfsr.counter); }
{ static const char label[] = { l,f,s,r,r,e,g, NUL }; ADD(spu.ch4.lfsr.reg); }
{ static const char label[] = { e,n,v,NO4,c,t,r, NUL }; ADD(spu.ch4.env.counter); }
{ static const char label[] = { e,n,v,NO4,v,o,l, NUL }; ADD(spu.ch4.env.volume); }
{ static const char label[] = { l,e,n,NO4,c,t,r, NUL }; ADD(spu.ch4.lcounter.counter); }
{ static const char label[] = { l,e,n,NO4,v,a,l, NUL }; ADD(spu.ch4.lcounter.lengthCounter); }
{ static const char label[] = { n,r,NO4,NO4, NUL }; ADD(spu.ch4.nr4); }
{ static const char label[] = { c,NO4,m,a,s,t,r, NUL }; ADD(spu.ch4.master); }
{ static const char label[] = { r,t,c,b,a,s,e, NUL }; ADD(rtc.baseTime); }
{ static const char label[] = { r,t,c,h,a,l,t, NUL }; ADD(rtc.haltTime); }
{ static const char label[] = { r,t,c,d,h, NUL }; ADD(rtc.dataDh); }
{ static const char label[] = { r,t,c,d,l, NUL }; ADD(rtc.dataDl); }
{ static const char label[] = { r,t,c,h, NUL }; ADD(rtc.dataH); }
{ static const char label[] = { r,t,c,m, NUL }; ADD(rtc.dataM); }
{ static const char label[] = { r,t,c,s, NUL }; ADD(rtc.dataS); }
{ static const char label[] = { r,t,c,l,l,d, NUL }; ADD(rtc.lastLatchData); }
{ static char const label[] = { h,NO3,b,a,s,e,t, NUL }; ADD(huc3.baseTime); }
{ static const char label[] = { h,NO3,h,a,l,t,t, NUL }; ADD(huc3.haltTime); }
{ static const char label[] = { h,NO3,d,a,t,a,t, NUL }; ADD(huc3.dataTime); }
{ static const char label[] = { h,NO3,w,r,i,t,t, NUL }; ADD(huc3.writingTime); }
{ static const char label[] = { h,NO3,h,a,l,t, NUL }; ADD(huc3.halted); }
{ static const char label[] = { h,NO3,s,h,f,t, NUL }; ADD(huc3.shift); }
{ static const char label[] = { h,NO3,r,v, NUL }; ADD(huc3.ramValue); }
{ static const char label[] = { h,NO3,m,f, NUL }; ADD(huc3.modeflag); }
{ static const char label[] = { h,NO3,i,r,c,y, NUL }; ADD(huc3.irBaseCycle); }
{ static const char label[] = { h,NO3,i,r,a,c, NUL }; ADD(huc3.irReceivingPulse); }
#undef ADD
#undef ADDPTR
#undef ADDARRAY
list.resize(list.size());
std::sort(list.begin(), list.end());
maxLabelsize_ = 0;
for (std::size_t i = 0; i < list.size(); ++i) {
if (list[i].labelsize > maxLabelsize_)
maxLabelsize_ = list[i].labelsize;
}
}
}
namespace {
static void writeSnapShot(omemstream &file) {
put24(file, 0);
}
/* Function-local static to make the construction lifetime
* explicit. The previous translation-unit-scope static was
* constructed at library load (before main / before any libretro
* entry point) which made its construction order against other
* library-load work implementation-defined. The list itself is
* read-only after construction so concurrent reads are safe. */
static const SaverList &saver_list(void) {
static SaverList instance;
return instance;
}
} // anon namespace
namespace gambatte {
void StateSaver::saveState(const SaveState &state, void *data) {
omemstream file(data);
if (file.fail())
return;
{ static const char ver[] = { 0, 1 }; file.write(ver, sizeof(ver)); }
writeSnapShot(file);
const SaverList &list = saver_list();
for (SaverList::const_iterator it = list.begin(); it != list.end(); ++it) {
file.write(it->label, it->labelsize);
(*it->save)(file, state);
}
}
bool StateSaver::loadState(SaveState &state, const void *data, size_t size) {
/* The bounds-checked imemstream needs the buffer end so it
* can stop at it. Previously this constructor only took a
* pointer; a malformed savestate could claim a 24-bit chunk
* size of 0xFFFFFF (16 MB) and the parser would happily skip
* past the end and read arbitrary memory. */
imemstream file(data, size);
if (file.fail() || file.get() != 0)
return false;
file.ignore();
file.ignore(get24(file));
const SaverList &list = saver_list();
const Array<char> labelbuf(list.maxLabelsize());
const Saver labelbufSaver = { labelbuf, 0, 0, list.maxLabelsize() };
SaverList::const_iterator done = list.begin();
while (file.good() && done != list.end()) {
file.getline(labelbuf, list.maxLabelsize(), NUL);
SaverList::const_iterator it = done;
if (std::strcmp(labelbuf, it->label)) {
it = std::lower_bound(it + 1, list.end(), labelbufSaver);
if (it == list.end() || std::strcmp(labelbuf, it->label)) {
file.ignore(get24(file));
continue;
}
} else
++done;
(*it->load)(file, state);
}
state.cpu.cycleCounter &= 0x7FFFFFFF;
state.spu.cycleCounter &= 0x7FFFFFFF;
/* If the parser tripped its failure latch the data was
* malformed; reject the load so the caller doesn't end up
* with a half-applied state. */
return !file.fail();
}
size_t StateSaver::stateSize(const SaveState &state) {
omemstream file(0);
if (file.fail())
return 0;
{ static const char ver[] = { 0, 1 }; file.write(ver, sizeof(ver)); }
writeSnapShot(file);
const SaverList &list = saver_list();
for (SaverList::const_iterator it = list.begin(); it != list.end(); ++it) {
file.write(it->label, it->labelsize);
(*it->save)(file, state);
}
return file.size();
}
}
+49
View File
@@ -0,0 +1,49 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef STATESAVER_H
#define STATESAVER_H
#include "gbint.h"
#include <string>
#include <stddef.h>
namespace gambatte {
struct SaveState;
class StateSaver {
StateSaver();
public:
enum { SS_SHIFT = 2 };
enum { SS_DIV = 1 << 2 };
enum { SS_WIDTH = 160 >> SS_SHIFT };
enum { SS_HEIGHT = 144 >> SS_SHIFT };
static void saveState(const SaveState &state, void *data);
/* size is the size of the input buffer in bytes. The parser
* uses it to bound every read so a malformed savestate cannot
* cause an OOB read past the end of the buffer. */
static bool loadState(SaveState &state, const void *data, size_t size);
static size_t stateSize(const SaveState &state);
};
}
#endif
+169
View File
@@ -0,0 +1,169 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "tima.h"
#include "savestate.h"
static unsigned char const timaClock[4] = { 10, 4, 6, 8 };
namespace gambatte {
Tima::Tima()
: lastUpdate_(0)
, tmatime_(disabled_time)
, tima_(0)
, tma_(0)
, tac_(0)
{
}
void Tima::saveState(SaveState &state) const {
state.mem.timaLastUpdate = lastUpdate_;
state.mem.tmatime = tmatime_;
}
void Tima::loadState(SaveState const &state, TimaInterruptRequester timaIrq) {
lastUpdate_ = state.mem.timaLastUpdate;
tmatime_ = state.mem.tmatime;
tima_ = state.mem.ioamhram.get()[0x105];
tma_ = state.mem.ioamhram.get()[0x106];
tac_ = state.mem.ioamhram.get()[0x107];
unsigned long nextIrqEventTime = disabled_time;
if (tac_ & 4) {
nextIrqEventTime = tmatime_ != disabled_time && tmatime_ > state.cpu.cycleCounter
? tmatime_
: lastUpdate_ + ((256u - tima_) << timaClock[tac_ & 3]) + 3;
}
timaIrq.setNextIrqEventTime(nextIrqEventTime);
}
void Tima::resetCc(unsigned long const oldCc, unsigned long const newCc, TimaInterruptRequester timaIrq) {
if (tac_ & 0x04) {
updateIrq(oldCc, timaIrq);
updateTima(oldCc);
unsigned long const dec = oldCc - newCc;
lastUpdate_ -= dec;
timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime() - dec);
if (tmatime_ != disabled_time)
tmatime_ -= dec;
}
}
void Tima::updateTima(unsigned long const cc) {
unsigned long const ticks = (cc - lastUpdate_) >> timaClock[tac_ & 3];
lastUpdate_ += ticks << timaClock[tac_ & 3];
if (cc >= tmatime_) {
if (cc >= tmatime_ + 4)
tmatime_ = disabled_time;
tima_ = tma_;
}
unsigned long tmp = tima_ + ticks;
while (tmp > 0x100)
tmp -= 0x100 - tma_;
if (tmp == 0x100) {
tmp = 0;
tmatime_ = lastUpdate_ + 3;
if (cc >= tmatime_) {
if (cc >= tmatime_ + 4)
tmatime_ = disabled_time;
tmp = tma_;
}
}
tima_ = tmp;
}
void Tima::setTima(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
if (tac_ & 0x04) {
updateIrq(cc, timaIrq);
updateTima(cc);
if (tmatime_ - cc < 4)
tmatime_ = disabled_time;
timaIrq.setNextIrqEventTime(lastUpdate_ + ((256u - data) << timaClock[tac_ & 3]) + 3);
}
tima_ = data;
}
void Tima::setTma(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
if (tac_ & 0x04) {
updateIrq(cc, timaIrq);
updateTima(cc);
}
tma_ = data;
}
void Tima::setTac(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
if (tac_ ^ data) {
unsigned long nextIrqEventTime = timaIrq.nextIrqEventTime();
if (tac_ & 0x04) {
updateIrq(cc, timaIrq);
updateTima(cc);
lastUpdate_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
tmatime_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
nextIrqEventTime -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
if (cc >= nextIrqEventTime)
timaIrq.flagIrq();
updateTima(cc);
tmatime_ = disabled_time;
nextIrqEventTime = disabled_time;
}
if (data & 4) {
lastUpdate_ = (cc >> timaClock[data & 3]) << timaClock[data & 3];
nextIrqEventTime = lastUpdate_ + ((256u - tima_) << timaClock[data & 3]) + 3;
}
timaIrq.setNextIrqEventTime(nextIrqEventTime);
}
tac_ = data;
}
unsigned Tima::tima(unsigned long cc) {
if (tac_ & 0x04)
updateTima(cc);
return tima_;
}
void Tima::doIrqEvent(TimaInterruptRequester timaIrq) {
timaIrq.flagIrq();
timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime()
+ ((256u - tma_) << timaClock[tac_ & 3]));
}
}
+66
View File
@@ -0,0 +1,66 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef TIMA_H
#define TIMA_H
#include "interruptrequester.h"
namespace gambatte {
class TimaInterruptRequester {
public:
explicit TimaInterruptRequester(InterruptRequester &intreq) : intreq_(intreq) {}
void flagIrq() const { intreq_.flagIrq(4); }
unsigned long nextIrqEventTime() const { return intreq_.eventTime(intevent_tima); }
void setNextIrqEventTime(unsigned long time) const { intreq_.setEventTime<intevent_tima>(time); }
private:
InterruptRequester &intreq_;
};
class Tima {
public:
Tima();
void saveState(SaveState &) const;
void loadState(const SaveState &, TimaInterruptRequester timaIrq);
void resetCc(unsigned long oldCc, unsigned long newCc, TimaInterruptRequester timaIrq);
void setTima(unsigned tima, unsigned long cc, TimaInterruptRequester timaIrq);
void setTma(unsigned tma, unsigned long cc, TimaInterruptRequester timaIrq);
void setTac(unsigned tac, unsigned long cc, TimaInterruptRequester timaIrq);
unsigned tima(unsigned long cc);
void doIrqEvent(TimaInterruptRequester timaIrq);
private:
unsigned long lastUpdate_;
unsigned long tmatime_;
unsigned char tima_;
unsigned char tma_;
unsigned char tac_;
void updateIrq(unsigned long const cc, TimaInterruptRequester timaIrq) {
while (cc >= timaIrq.nextIrqEventTime())
doIrqEvent(timaIrq);
}
void updateTima(unsigned long cc);
};
}
#endif
+777
View File
@@ -0,0 +1,777 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "video.h"
#include "savestate.h"
#include <cstring>
#include <algorithm>
#include <string>
namespace gambatte
{
void LCD::reset(const unsigned char *const oamram, unsigned char const *vram, const bool cgb)
{
ppu_.reset(oamram, vram, cgb);
lycIrq_.setCgb(cgb);
refreshPalettes();
}
static unsigned long mode2IrqSchedule(const unsigned statReg, const LyCounter &lyCounter, const unsigned long cycleCounter)
{
if (!(statReg & 0x20))
return disabled_time;
unsigned next = lyCounter.time() - cycleCounter;
if (lyCounter.ly() >= 143 || (lyCounter.ly() == 142 && next <= 4) || (statReg & 0x08))
next += (153u - lyCounter.ly()) * lyCounter.lineTime();
else
{
if (next <= 4)
next += lyCounter.lineTime();
next -= 4;
}
return cycleCounter + next;
}
static inline unsigned long m0IrqTimeFromXpos166Time(
const unsigned long xpos166Time, const bool cgb, const bool ds)
{
return xpos166Time + cgb - ds;
}
static inline unsigned long hdmaTimeFromM0Time(
const unsigned long m0Time, const bool ds)
{
return m0Time + 1 - ds;
}
static unsigned long nextHdmaTime(const unsigned long lastM0Time,
const unsigned long nextM0Time, const unsigned long cycleCounter, const bool ds)
{
return cycleCounter < hdmaTimeFromM0Time(lastM0Time, ds)
? hdmaTimeFromM0Time(lastM0Time, ds)
: hdmaTimeFromM0Time(nextM0Time, ds);
}
void LCD::setStatePtrs(SaveState &state)
{
state.ppu.bgpData.set( bgpData_, sizeof bgpData_);
state.ppu.objpData.set(objpData_, sizeof objpData_);
ppu_.setStatePtrs(state);
}
void LCD::saveState(SaveState &state) const
{
state.mem.hdmaTransfer = hdmaIsEnabled();
state.ppu.nextM0Irq = eventTimes_(MODE0_IRQ) - ppu_.now();
state.ppu.pendingLcdstatIrq = eventTimes_(ONESHOT_LCDSTATIRQ) != disabled_time;
if (isCgb())
std::memcpy(state.ppu.dmgPalette, dmgColorsGBC_, 8 * 3);
lycIrq_.saveState(state);
m0Irq_.saveState(state);
ppu_.saveState(state);
}
void LCD::loadState(const SaveState &state, const unsigned char *const oamram)
{
statReg_ = state.mem.ioamhram.get()[0x141];
m2IrqStatReg_ = statReg_;
m1IrqStatReg_ = statReg_;
ppu_.loadState(state, oamram);
lycIrq_.loadState(state);
m0Irq_.loadState(state);
if (ppu_.lcdc() & 0x80)
{
nextM0Time_.predictNextM0Time(ppu_);
lycIrq_.reschedule(ppu_.lyCounter(), ppu_.now());
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(state.ppu.pendingLcdstatIrq
? ppu_.now() + 1 : static_cast<unsigned long>(disabled_time));
eventTimes_.setm<ONESHOT_UPDATEWY2>(state.ppu.oldWy != state.mem.ioamhram.get()[0x14A]
? ppu_.now() + 1 : static_cast<unsigned long>(disabled_time));
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), ppu_.now()));
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, ppu_.now()));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), ppu_.now()));
eventTimes_.setm<MODE0_IRQ>((statReg_ & 0x08) ? ppu_.now() + state.ppu.nextM0Irq : static_cast<unsigned long>(disabled_time));
eventTimes_.setm<HDMA_REQ>(state.mem.hdmaTransfer
? nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), ppu_.now(), isDoubleSpeed())
: static_cast<unsigned long>(disabled_time));
}
else
{
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
eventTimes_.set(static_cast<MemEvent>(i), disabled_time);
}
if (isCgb())
std::memcpy(dmgColorsGBC_, state.ppu.dmgPalette, 8 * 3);
refreshPalettes();
}
void LCD::refreshPalettes()
{
if (ppu_.cgb() && !ppu_.inDmgMode())
{
for (unsigned i = 0; i < 8 * 8; i += 2)
{
ppu_.bgPalette()[i >> 1] = gbcToRgb32( bgpData_[i] | bgpData_[i + 1] << 8);
ppu_.spPalette()[i >> 1] = gbcToRgb32(objpData_[i] | objpData_[i + 1] << 8);
}
/* CGB mode: rebuild all 8 BG palette expansions, the CGB
* renderer's hot do-while indexes [nattrib & 7] per tile. */
for (unsigned p = 0; p < 8; ++p)
ppu_.refreshBgPaletteExpansion(p);
}
else
{
if (ppu_.inDmgMode())
{
for (unsigned i = 0; i < 8 * 3; i += 2)
dmgColorsRgb32_[i >> 1] = gbcToRgb32( dmgColorsGBC_[i] | dmgColorsGBC_[i + 1] << 8);
}
setDmgPalette(ppu_.bgPalette() , dmgColorsRgb32_ , bgpData_[0]);
setDmgPalette(ppu_.spPalette() , dmgColorsRgb32_ + 4, objpData_[0]);
setDmgPalette(ppu_.spPalette() + 4, dmgColorsRgb32_ + 8, objpData_[1]);
/* DMG mode: only palette 0 is used by doFullTilesUnrolledDmg.
* Rebuild slot 0; slots 1..7 are unused but stay zero-init. */
ppu_.refreshBgPaletteExpansion(0);
}
}
void LCD::resetCc(const unsigned long oldCc, const unsigned long newCc)
{
update(oldCc);
ppu_.resetCc(oldCc, newCc);
if (ppu_.lcdc() & 0x80)
{
const unsigned long dec = oldCc - newCc;
nextM0Time_.invalidatePredictedNextM0Time();
lycIrq_.reschedule(ppu_.lyCounter(), newCc);
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
{
if (eventTimes_(static_cast<MemEvent>(i)) != disabled_time)
eventTimes_.set(static_cast<MemEvent>(i), eventTimes_(static_cast<MemEvent>(i)) - dec);
}
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
}
}
void LCD::speedChange(const unsigned long cycleCounter)
{
update(cycleCounter);
ppu_.speedChange(cycleCounter);
if (ppu_.lcdc() & 0x80)
{
nextM0Time_.predictNextM0Time(ppu_);
lycIrq_.reschedule(ppu_.lyCounter(), cycleCounter);
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cycleCounter));
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, cycleCounter));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cycleCounter));
if (eventTimes_(MODE0_IRQ) != disabled_time && eventTimes_(MODE0_IRQ) - cycleCounter > 1)
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
if (hdmaIsEnabled() && eventTimes_(HDMA_REQ) - cycleCounter > 1)
{
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(),
nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
}
}
}
static inline unsigned long m0TimeOfCurrentLine(const unsigned long nextLyTime,
const unsigned long lastM0Time, const unsigned long nextM0Time)
{
return nextM0Time < nextLyTime ? nextM0Time : lastM0Time;
}
unsigned long LCD::m0TimeOfCurrentLine(const unsigned long cc)
{
if (cc >= nextM0Time_.predictedNextM0Time())
{
update(cc);
nextM0Time_.predictNextM0Time(ppu_);
}
return gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(), ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time());
}
static bool isHdmaPeriod(const LyCounter &lyCounter,
const unsigned long m0TimeOfCurrentLy, const unsigned long cycleCounter)
{
const unsigned timeToNextLy = lyCounter.time() - cycleCounter;
return /*(ppu_.lcdc & 0x80) && */lyCounter.ly() < 144 && timeToNextLy > 4
&& cycleCounter >= hdmaTimeFromM0Time(m0TimeOfCurrentLy, lyCounter.isDoubleSpeed());
}
void LCD::enableHdma(const unsigned long cycleCounter)
{
if (cycleCounter >= nextM0Time_.predictedNextM0Time())
{
update(cycleCounter);
nextM0Time_.predictNextM0Time(ppu_);
}
else if (cycleCounter >= eventTimes_.nextEventTime())
update(cycleCounter);
if (isHdmaPeriod(ppu_.lyCounter(),
gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(),
ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time()), cycleCounter))
eventTimes_.flagHdmaReq();
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
}
void LCD::disableHdma(const unsigned long cycleCounter)
{
if (cycleCounter >= eventTimes_.nextEventTime())
update(cycleCounter);
eventTimes_.setm<HDMA_REQ>(disabled_time);
}
bool LCD::vramAccessible(const unsigned long cc)
{
if (cc >= eventTimes_.nextEventTime())
update(cc);
return !(ppu_.lcdc() & 0x80) || ppu_.lyCounter().ly() >= 144
|| ppu_.lyCounter().lineCycles(cc) < 80U
|| cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
}
bool LCD::cgbpAccessible(const unsigned long cc)
{
if (cc >= eventTimes_.nextEventTime())
update(cc);
return !(ppu_.lcdc() & 0x80) || ppu_.lyCounter().ly() >= 144
|| ppu_.lyCounter().lineCycles(cc) < 80U + isDoubleSpeed()
|| cc >= m0TimeOfCurrentLine(cc) + 3 - isDoubleSpeed();
}
void LCD::doCgbBgColorChange(unsigned index, const unsigned data, const unsigned long cc)
{
if (cgbpAccessible(cc))
{
update(cc);
doCgbColorChange(bgpData_, ppu_.bgPalette(), index, data);
/* CGB BCPD writes one 16-bit colour entry at a time
* (palette[index >> 1] in doCgbColorChange). Each CGB palette
* has 4 entries x 2 bytes = 8 bytes, so the affected palette
* is (index >> 1) >> 2 = index >> 3. Only that slot needs
* its expansion rebuilt. */
ppu_.refreshBgPaletteExpansion(index >> 3);
}
}
void LCD::doCgbSpColorChange(unsigned index, const unsigned data, const unsigned long cc)
{
if (cgbpAccessible(cc))
{
update(cc);
doCgbColorChange(objpData_, ppu_.spPalette(), index, data);
}
}
bool LCD::oamReadable(const unsigned long cc)
{
if (!(ppu_.lcdc() & 0x80) || ppu_.inactivePeriodAfterDisplayEnable(cc))
return true;
if (cc >= eventTimes_.nextEventTime())
update(cc);
if (ppu_.lyCounter().lineCycles(cc) + 4 - ppu_.lyCounter().isDoubleSpeed() * 3u >= 456)
return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153;
return ppu_.lyCounter().ly() >= 144 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
}
bool LCD::oamWritable(const unsigned long cc)
{
if (!(ppu_.lcdc() & 0x80) || ppu_.inactivePeriodAfterDisplayEnable(cc))
return true;
if (cc >= eventTimes_.nextEventTime())
update(cc);
if (ppu_.lyCounter().lineCycles(cc) + 3 + ppu_.cgb() - ppu_.lyCounter().isDoubleSpeed() * 2u >= 456)
return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153;
return ppu_.lyCounter().ly() >= 144 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
}
void LCD::mode3CyclesChange()
{
nextM0Time_.invalidatePredictedNextM0Time();
if (eventTimes_(MODE0_IRQ) != disabled_time
&& eventTimes_(MODE0_IRQ) > m0IrqTimeFromXpos166Time(ppu_.now(), ppu_.cgb(), isDoubleSpeed())) {
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
}
if (eventTimes_(HDMA_REQ) != disabled_time
&& eventTimes_(HDMA_REQ) > hdmaTimeFromM0Time(ppu_.lastM0Time(), isDoubleSpeed())) {
nextM0Time_.predictNextM0Time(ppu_);
eventTimes_.setm<HDMA_REQ>(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed()));
}
}
void LCD::wxChange(const unsigned newValue, const unsigned long cycleCounter)
{
update(cycleCounter + isDoubleSpeed() + 1);
ppu_.setWx(newValue);
mode3CyclesChange();
}
void LCD::wyChange(const unsigned newValue, const unsigned long cc)
{
update(cc + 1);
ppu_.setWy(newValue);
// mode3CyclesChange(); // should be safe to wait until after wy2 delay, because no mode3 events are close to when wy1 is read.
// wy2 is a delayed version of wy. really just slowness of ly == wy comparison.
if (ppu_.cgb() && (ppu_.lcdc() & 0x80)) {
eventTimes_.setm<ONESHOT_UPDATEWY2>(cc + 5);
} else {
update(cc + 2);
ppu_.updateWy2();
mode3CyclesChange();
}
}
void LCD::scxChange(const unsigned newScx, const unsigned long cycleCounter) {
update(cycleCounter + ppu_.cgb() + isDoubleSpeed());
ppu_.setScx(newScx);
mode3CyclesChange();
}
void LCD::scyChange(const unsigned newValue, const unsigned long cycleCounter) {
update(cycleCounter + ppu_.cgb() + isDoubleSpeed());
ppu_.setScy(newValue);
}
void LCD::oamChange(const unsigned long cc) {
if (ppu_.lcdc() & 0x80) {
update(cc);
ppu_.oamChange(cc);
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
}
}
void LCD::oamChange(const unsigned char *const oamram, const unsigned long cc) {
update(cc);
ppu_.oamChange(oamram, cc);
if (ppu_.lcdc() & 0x80)
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
}
void LCD::lcdcChange(const unsigned data, const unsigned long cc) {
const unsigned oldLcdc = ppu_.lcdc();
update(cc);
if ((oldLcdc ^ data) & 0x80)
{
ppu_.setLcdc(data, cc);
if (data & 0x80) {
lycIrq_.lcdReset();
m0Irq_.lcdReset(statReg_, lycIrq_.lycReg());
if (lycIrq_.lycReg() == 0 && (statReg_ & 0x40))
eventTimes_.flagIrq(2);
nextM0Time_.predictNextM0Time(ppu_);
lycIrq_.reschedule(ppu_.lyCounter(), cc);
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, cc));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cc));
if (statReg_ & 0x08)
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
if (hdmaIsEnabled())
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(),
nextM0Time_.predictedNextM0Time(), cc, isDoubleSpeed()));
}
else
{
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
eventTimes_.set(static_cast<MemEvent>(i), disabled_time);
}
}
else if (data & 0x80)
{
if (ppu_.cgb())
{
ppu_.setLcdc((oldLcdc & ~0x14) | (data & 0x14), cc);
if ((oldLcdc ^ data) & 0x04)
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
update(cc + isDoubleSpeed() + 1);
ppu_.setLcdc(data, cc + isDoubleSpeed() + 1);
if ((oldLcdc ^ data) & 0x20)
mode3CyclesChange();
}
else
{
ppu_.setLcdc(data, cc);
if ((oldLcdc ^ data) & 0x04)
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
if ((oldLcdc ^ data) & 0x22)
mode3CyclesChange();
}
}
else
ppu_.setLcdc(data, cc);
}
namespace {
struct LyCnt {
unsigned ly; int timeToNextLy;
LyCnt(unsigned ly, int timeToNextLy) : ly(ly), timeToNextLy(timeToNextLy) {}
};
static LyCnt const getLycCmpLy(LyCounter const &lyCounter, unsigned long cc) {
unsigned ly = lyCounter.ly();
int timeToNextLy = lyCounter.time() - cc;
if (ly == 153)
{
unsigned is_doublespeed = (unsigned)lyCounter.isDoubleSpeed();
if (timeToNextLy - (448 << is_doublespeed) > 0)
timeToNextLy -= (448 << is_doublespeed);
else
{
ly = 0;
timeToNextLy += lyCounter.lineTime();
}
}
return LyCnt(ly, timeToNextLy);
}
}
void LCD::lcdstatChange(const unsigned data, const unsigned long cc)
{
if (cc >= eventTimes_.nextEventTime())
update(cc);
const unsigned old = statReg_;
statReg_ = data;
lycIrq_.statRegChange(data, ppu_.lyCounter(), cc);
if (ppu_.lcdc() & 0x80)
{
int const timeToNextLy = ppu_.lyCounter().time() - cc;
LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
if (!ppu_.cgb())
{
if (ppu_.lyCounter().ly() < 144)
{
if (cc + 1 < m0TimeOfCurrentLine(cc))
{
if (lycCmp.ly == lycIrq_.lycReg() && !(old & 0x40))
eventTimes_.flagIrq(2);
}
else
{
if (!(old & 0x08) && !(lycCmp.ly == lycIrq_.lycReg() && (old & 0x40)))
eventTimes_.flagIrq(2);
}
}
else
{
if (!(old & 0x10) && !(lycCmp.ly == lycIrq_.lycReg() && (old & 0x40)))
eventTimes_.flagIrq(2);
}
}
else if (data & ~old & 0x78)
{
bool const lycperiod = lycCmp.ly == lycIrq_.lycReg() && lycCmp.timeToNextLy > 4 - isDoubleSpeed() * 4;
if (!(lycperiod && (old & 0x40)))
{
if (ppu_.lyCounter().ly() < 144)
{
if (cc + isDoubleSpeed() * 2 < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4)
{
if (lycperiod && (data & 0x40))
eventTimes_.flagIrq(2);
}
else if (!(old & 0x08))
{
if ((data & 0x08) || (lycperiod && (data & 0x40)))
eventTimes_.flagIrq(2);
}
}
else if (!(old & 0x10))
{
if ((data & 0x10) && (ppu_.lyCounter().ly() < 153 || timeToNextLy > 4 - isDoubleSpeed() * 4))
{
eventTimes_.flagIrq(2);
}
else if (lycperiod && (data & 0x40))
eventTimes_.flagIrq(2);
}
}
if ((data & 0x28) == 0x20 && !(old & 0x20)
&& ((timeToNextLy <= 4 && ppu_.lyCounter().ly() < 143)
|| (timeToNextLy == 456*2 && ppu_.lyCounter().ly() < 144)))
{
eventTimes_.flagIrq(2);
}
}
if ((data & 0x08) && eventTimes_(MODE0_IRQ) == disabled_time)
{
update(cc);
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
}
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(data, ppu_.lyCounter(), cc));
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
}
m2IrqStatReg_ = eventTimes_(MODE2_IRQ) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U
? data : (m2IrqStatReg_ & 0x10) | (statReg_ & ~0x10);
m1IrqStatReg_ = eventTimes_(MODE1_IRQ) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U
? data : (m1IrqStatReg_ & 0x08) | (statReg_ & ~0x08);
m0Irq_.statRegChange(data, eventTimes_(MODE0_IRQ), cc, ppu_.cgb());
}
void LCD::lycRegChange(const unsigned data, const unsigned long cc)
{
unsigned const old = lycIrq_.lycReg();
if (data == old)
return;
if (cc >= eventTimes_.nextEventTime())
update(cc);
m0Irq_.lycRegChange(data, eventTimes_(MODE0_IRQ), cc, isDoubleSpeed(), ppu_.cgb());
lycIrq_.lycRegChange(data, ppu_.lyCounter(), cc);
if (!(ppu_.lcdc() & 0x80))
return;
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
int const timeToNextLy = ppu_.lyCounter().time() - cc;
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
if ((statReg_ & 0x40) && data < 154
&& (ppu_.lyCounter().ly() < 144
? !(statReg_ & 0x08) || cc < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4 << ppu_.cgb()
: !(statReg_ & 0x10) || (ppu_.lyCounter().ly() == 153 && timeToNextLy <= 4 && ppu_.cgb() && !(bool)is_doublespeed)))
{
LyCnt lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
if (lycCmp.timeToNextLy <= 4 << ppu_.cgb())
{
lycCmp.ly = old != lycCmp.ly || (lycCmp.timeToNextLy <= 4 && ppu_.cgb() && !(bool)is_doublespeed)
? (lycCmp.ly == 153 ? 0 : lycCmp.ly + 1)
: 0xFF; // simultaneous ly/lyc inc. lyc flag never goes low -> no trigger.
}
if (data == lycCmp.ly)
{
if (ppu_.cgb() && !(bool)is_doublespeed)
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(cc + 5);
else
eventTimes_.flagIrq(2);
}
}
}
unsigned LCD::getStat(const unsigned lycReg, const unsigned long cc)
{
unsigned stat = 0;
if (ppu_.lcdc() & 0x80)
{
if (cc >= eventTimes_.nextEventTime())
update(cc);
int const timeToNextLy = ppu_.lyCounter().time() - cc;
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
if (ppu_.lyCounter().ly() > 143)
{
if (ppu_.lyCounter().ly() < 153 || timeToNextLy > 4 - is_doublespeed * 4)
stat = 1;
}
else
{
unsigned const lineCycles = 456 - (timeToNextLy >> is_doublespeed);
if (lineCycles < 80)
{
if (!ppu_.inactivePeriodAfterDisplayEnable(cc))
stat = 2;
}
else if (cc + is_doublespeed - ppu_.cgb() + 2 < m0TimeOfCurrentLine(cc))
stat = 3;
}
LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
if (lycReg == lycCmp.ly && lycCmp.timeToNextLy > 4 - is_doublespeed * 4)
stat |= 4;
}
return stat;
}
inline void LCD::doMode2IrqEvent()
{
const unsigned ly = eventTimes_(LY_COUNT) - eventTimes_(MODE2_IRQ) < 8
? (ppu_.lyCounter().ly() == 153 ? 0 : ppu_.lyCounter().ly() + 1)
: ppu_.lyCounter().ly();
if ((ly != 0 || !(m2IrqStatReg_ & 0x10)) &&
(!(m2IrqStatReg_ & 0x40) || (lycIrq_.lycReg() != 0 ? ly != (lycIrq_.lycReg() + 1U) : ly > 1)))
eventTimes_.flagIrq(2);
m2IrqStatReg_ = statReg_;
if (!(statReg_ & 0x08))
{
unsigned long nextTime = eventTimes_(MODE2_IRQ) + ppu_.lyCounter().lineTime();
if (ly == 0)
nextTime -= 4;
else if (ly == 143)
nextTime += ppu_.lyCounter().lineTime() * 10 + 4;
eventTimes_.setm<MODE2_IRQ>(nextTime);
}
else
eventTimes_.setm<MODE2_IRQ>(eventTimes_(MODE2_IRQ) + (70224 << (unsigned)isDoubleSpeed()));
}
inline void LCD::event()
{
switch (eventTimes_.nextEvent())
{
case MEM_EVENT:
switch (eventTimes_.nextMemEvent())
{
case MODE1_IRQ:
eventTimes_.flagIrq((m1IrqStatReg_ & 0x18) == 0x10 ? 3 : 1);
m1IrqStatReg_ = statReg_;
eventTimes_.setm<MODE1_IRQ>(eventTimes_(MODE1_IRQ) + (70224 << (unsigned)isDoubleSpeed()));
break;
case LYC_IRQ:
{
unsigned char ifreg = 0;
lycIrq_.doEvent(&ifreg, ppu_.lyCounter());
eventTimes_.flagIrq(ifreg);
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
}
break;
case SPRITE_MAP:
eventTimes_.setm<SPRITE_MAP>(ppu_.doSpriteMapEvent(eventTimes_(SPRITE_MAP)));
mode3CyclesChange();
break;
case HDMA_REQ:
eventTimes_.flagHdmaReq();
nextM0Time_.predictNextM0Time(ppu_);
eventTimes_.setm<HDMA_REQ>(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed()));
break;
case MODE2_IRQ:
doMode2IrqEvent();
break;
case MODE0_IRQ:
{
unsigned char ifreg = 0;
m0Irq_.doEvent(&ifreg, ppu_.lyCounter().ly(), statReg_, lycIrq_.lycReg());
eventTimes_.flagIrq(ifreg);
}
eventTimes_.setm<MODE0_IRQ>((statReg_ & 0x08)
? m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed())
: static_cast<unsigned long>(disabled_time));
break;
case ONESHOT_LCDSTATIRQ:
eventTimes_.flagIrq(2);
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(disabled_time);
break;
case ONESHOT_UPDATEWY2:
ppu_.updateWy2();
mode3CyclesChange();
eventTimes_.setm<ONESHOT_UPDATEWY2>(disabled_time);
break;
}
break;
case LY_COUNT:
ppu_.doLyCountEvent();
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
break;
}
}
void LCD::update(const unsigned long cycleCounter)
{
if (!(ppu_.lcdc() & 0x80))
return;
while (cycleCounter >= eventTimes_.nextEventTime())
{
ppu_.update(eventTimes_.nextEventTime());
event();
}
ppu_.update(cycleCounter);
}
}
+244
View File
@@ -0,0 +1,244 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef VIDEO_H
#define VIDEO_H
#include "interruptrequester.h"
#include "video/lyc_irq.h"
#include "video/m0_irq.h"
#include "video/next_m0_time.h"
#include "video/ppu.h"
#include <memory>
namespace gambatte {
class VideoInterruptRequester
{
public:
explicit VideoInterruptRequester(InterruptRequester &intreq) : intreq_(intreq) {}
void flagHdmaReq() const { gambatte::flagHdmaReq(intreq_); }
void flagIrq(const unsigned bit) const { intreq_.flagIrq(bit); }
void setNextEventTime(const unsigned long time) const { intreq_.setEventTime<intevent_video>(time); }
private:
InterruptRequester &intreq_;
};
class LCD
{
public:
LCD(const unsigned char *oamram, const unsigned char *vram_in, VideoInterruptRequester memEventRequester);
void reset(const unsigned char *oamram, unsigned char const *vram, bool cgb);
void setStatePtrs(SaveState &state);
void saveState(SaveState &state) const;
void loadState(const SaveState &state, const unsigned char *oamram);
void setDmgPaletteColor(unsigned palNum, unsigned colorNum, video_pixel_t rgb32);
void setVideoBuffer(video_pixel_t *videoBuf, int pitch);
void setDmgMode(bool mode) { ppu_.setDmgMode(mode); }
void swapToDMG() {
ppu_.setDmgMode(true);
refreshPalettes();
}
void dmgBgPaletteChange(const unsigned data, const unsigned long cycleCounter) {
update(cycleCounter);
bgpData_[0] = data;
setDmgPalette(ppu_.bgPalette(), dmgColorsRgb32_, data);
ppu_.refreshBgPaletteExpansion(0);
}
void dmgSpPalette1Change(const unsigned data, const unsigned long cycleCounter) {
update(cycleCounter);
objpData_[0] = data;
setDmgPalette(ppu_.spPalette(), dmgColorsRgb32_ + 4, data);
}
void dmgSpPalette2Change(const unsigned data, const unsigned long cycleCounter) {
update(cycleCounter);
objpData_[1] = data;
setDmgPalette(ppu_.spPalette() + 4, dmgColorsRgb32_ + 8, data);
}
void cgbBgColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) {
if (bgpData_[index] != data) {
doCgbBgColorChange(index, data, cycleCounter);
if(index < 8)
doCgbColorChange(dmgColorsGBC_, dmgColorsRgb32_, index, data);
}
}
void cgbSpColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) {
if (objpData_[index] != data) {
doCgbSpColorChange(index, data, cycleCounter);
if(index < 8 * 2/*dmg has 2 sprite banks*/)
doCgbColorChange(dmgColorsGBC_ + 8, dmgColorsRgb32_ + 4, index, data);
}
}
unsigned cgbBgColorRead(const unsigned index, const unsigned long cycleCounter) {
return (ppu_.cgb() & cgbpAccessible(cycleCounter)) ? bgpData_[index] : 0xFF;
}
unsigned cgbSpColorRead(const unsigned index, const unsigned long cycleCounter) {
return (ppu_.cgb() & cgbpAccessible(cycleCounter)) ? objpData_[index] : 0xFF;
}
void updateScreen(bool blanklcd, unsigned long cc);
void resetCc(unsigned long oldCC, unsigned long newCc);
void speedChange(unsigned long cycleCounter);
bool vramAccessible(unsigned long cycleCounter);
bool oamReadable(unsigned long cycleCounter);
bool oamWritable(unsigned long cycleCounter);
void wxChange(unsigned newValue, unsigned long cycleCounter);
void wyChange(unsigned newValue, unsigned long cycleCounter);
void oamChange(unsigned long cycleCounter);
void oamChange(const unsigned char *oamram, unsigned long cycleCounter);
void scxChange(unsigned newScx, unsigned long cycleCounter);
void scyChange(unsigned newValue, unsigned long cycleCounter);
void vramChange(const unsigned long cycleCounter) { update(cycleCounter); }
unsigned getStat(unsigned lycReg, unsigned long cycleCounter);
unsigned getLyReg(const unsigned long cycleCounter) {
unsigned lyReg = 0;
if (ppu_.lcdc() & 0x80) {
if (cycleCounter >= ppu_.lyCounter().time())
update(cycleCounter);
lyReg = ppu_.lyCounter().ly();
if (lyReg == 153) {
if (isDoubleSpeed()) {
if (ppu_.lyCounter().time() - cycleCounter <= 456 * 2 - 8)
lyReg = 0;
} else
lyReg = 0;
} else if (ppu_.lyCounter().time() - cycleCounter <= 4)
++lyReg;
}
return lyReg;
}
unsigned long nextMode1IrqTime() const { return eventTimes_(MODE1_IRQ); }
void lcdcChange(unsigned data, unsigned long cycleCounter);
void lcdstatChange(unsigned data, unsigned long cycleCounter);
void lycRegChange(unsigned data, unsigned long cycleCounter);
void enableHdma(unsigned long cycleCounter);
void disableHdma(unsigned long cycleCounter);
bool hdmaIsEnabled() const { return eventTimes_(HDMA_REQ) != disabled_time; }
void update(unsigned long cycleCounter);
bool isCgb() const { return ppu_.cgb(); }
bool isDoubleSpeed() const { return ppu_.lyCounter().isDoubleSpeed(); }
void setColorCorrection(bool colorCorrection);
void setColorCorrectionMode(unsigned colorCorrectionMode);
void setColorCorrectionBrightness(float colorCorrectionBrightness);
void setDarkFilterLevel(unsigned darkFilterLevel);
video_pixel_t gbcToRgb32(const unsigned bgr15);
private:
enum Event { MEM_EVENT, LY_COUNT }; enum { NUM_EVENTS = LY_COUNT + 1 };
enum MemEvent { ONESHOT_LCDSTATIRQ, ONESHOT_UPDATEWY2, MODE1_IRQ, LYC_IRQ, SPRITE_MAP,
HDMA_REQ, MODE2_IRQ, MODE0_IRQ }; enum { NUM_MEM_EVENTS = MODE0_IRQ + 1 };
class EventTimes
{
public:
explicit EventTimes(const VideoInterruptRequester memEventRequester) : memEventRequester_(memEventRequester) {}
Event nextEvent() const { return static_cast<Event>(eventMin_.min()); }
unsigned long nextEventTime() const { return eventMin_.minValue(); }
unsigned long operator()(const Event e) const { return eventMin_.value(e); }
template<Event e> void set(const unsigned long time) { eventMin_.setValue<e>(time); }
void set(const Event e, const unsigned long time) { eventMin_.setValue(e, time); }
MemEvent nextMemEvent() const { return static_cast<MemEvent>(memEventMin_.min()); }
unsigned long nextMemEventTime() const { return memEventMin_.minValue(); }
unsigned long operator()(const MemEvent e) const { return memEventMin_.value(e); }
template<MemEvent e> void setm(const unsigned long time) { memEventMin_.setValue<e>(time); setMemEvent(); }
void set(const MemEvent e, const unsigned long time) { memEventMin_.setValue(e, time); setMemEvent(); }
void flagIrq(const unsigned bit) { memEventRequester_.flagIrq(bit); }
void flagHdmaReq() { memEventRequester_.flagHdmaReq(); }
private:
MinKeeper<NUM_EVENTS> eventMin_;
MinKeeper<NUM_MEM_EVENTS> memEventMin_;
VideoInterruptRequester memEventRequester_;
void setMemEvent() {
const unsigned long nmet = nextMemEventTime();
eventMin_.setValue<MEM_EVENT>(nmet);
memEventRequester_.setNextEventTime(nmet);
}
};
PPU ppu_;
video_pixel_t dmgColorsRgb32_[3 * 4];
unsigned char dmgColorsGBC_[3 * 8];
unsigned char bgpData_[8 * 8];
unsigned char objpData_[8 * 8];
EventTimes eventTimes_;
M0Irq m0Irq_;
LycIrq lycIrq_;
NextM0Time nextM0Time_;
unsigned char statReg_;
unsigned char m2IrqStatReg_;
unsigned char m1IrqStatReg_;
static void setDmgPalette(video_pixel_t *palette, const video_pixel_t *dmgColors, unsigned data);
void setDmgPaletteColor(unsigned index, video_pixel_t rgb32);
void setDBuffer();
void refreshPalettes();
void doMode2IrqEvent();
void event();
unsigned long m0TimeOfCurrentLine(unsigned long cc);
bool cgbpAccessible(unsigned long cycleCounter);
void mode3CyclesChange();
void doCgbBgColorChange(unsigned index, unsigned data, unsigned long cycleCounter);
void doCgbSpColorChange(unsigned index, unsigned data, unsigned long cycleCounter);
bool colorCorrection;
unsigned colorCorrectionMode;
float colorCorrectionBrightness;
unsigned darkFilterLevel;
void doCgbColorChange(unsigned char *const pdata,
video_pixel_t *const palette, unsigned index, const unsigned data);
void darkenRgb(float &r, float &g, float &b);
};
}
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef LCDDEF_H
#define LCDDEF_H
namespace gambatte {
enum { lcdc_bgen = 0x01,
lcdc_objen = 0x02,
lcdc_obj2x = 0x04,
lcdc_tdsel = 0x10,
lcdc_we = 0x20,
lcdc_en = 0x80 };
enum { lcdstat_lycflag = 0x04,
lcdstat_m0irqen = 0x08,
lcdstat_m1irqen = 0x10,
lcdstat_m2irqen = 0x20,
lcdstat_lycirqen = 0x40 };
}
#endif
@@ -0,0 +1,68 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "ly_counter.h"
#include "../savestate.h"
namespace gambatte {
LyCounter::LyCounter()
: time_(0)
, lineTime_(0)
, ly_(0)
, ds_(false)
{
setDoubleSpeed(false);
reset(0, 0);
}
void LyCounter::doEvent() {
++ly_;
if (ly_ == 154)
ly_ = 0;
time_ = time_ + lineTime_;
}
unsigned long LyCounter::nextLineCycle(unsigned const lineCycle, unsigned long const cc) const {
unsigned long tmp = time_ + (lineCycle << (unsigned)ds_);
if (tmp - cc > lineTime_)
tmp -= lineTime_;
return tmp;
}
unsigned long LyCounter::nextFrameCycle(unsigned long const frameCycle, unsigned long const cc) const {
unsigned long tmp = time_ + (((153U - ly()) * 456U + frameCycle) << (unsigned)ds_);
if (tmp - cc > 70224U << (unsigned)ds_)
tmp -= 70224U << (unsigned)ds_;
return tmp;
}
void LyCounter::reset(unsigned long videoCycles, unsigned long lastUpdate) {
ly_ = videoCycles / 456;
time_ = lastUpdate + ((456 - (videoCycles - ly_ * 456ul)) << (unsigned)isDoubleSpeed());
}
void LyCounter::setDoubleSpeed(bool ds) {
ds_ = ds;
lineTime_ = 456U << (unsigned)ds;
}
}

Some files were not shown because too many files have changed in this diff Show More