Compare commits

..

No commits in common. "master" and "v114" have entirely different histories.
master ... v114

514 changed files with 6516 additions and 56190 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,9 +1,32 @@
freebsd-x86_64-binaries_task: linux-x86_64-binaries_task:
freebsd_instance: container:
image_family: freebsd-12-2 image: ubuntu:latest
setup_script: setup_script:
- pkg install --yes gmake gdb gcc8 pkgconf sdl2 openal-soft gtk3 gtksourceview3 libXv zip - apt-get update && apt-get -y install build-essential libgtk2.0-dev libpulse-dev mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl2-dev libxv-dev libao-dev libopenal-dev libudev-dev zip
compile_script:
- make -C bsnes local=false
package_script:
- mkdir bsnes-nightly
- mkdir bsnes-nightly/Database
- mkdir bsnes-nightly/Firmware
- cp -a bsnes/out/bsnes bsnes-nightly/bsnes
- cp -a bsnes/Database/* bsnes-nightly/Database
- cp -a shaders bsnes-nightly/Shaders
- cp -a GPLv3.txt bsnes-nightly
- zip -r bsnes-nightly.zip bsnes-nightly
bsnes-nightly_artifacts:
path: "bsnes-nightly.zip"
freebsd-x86_64-binaries_task:
freebsd_instance:
image: freebsd-12-0-release-amd64
setup_script:
- pkg install --yes gmake gdb gcc8 pkgconf sdl2 openal-soft gtksourceview2 libXv zip
compile_script: compile_script:
- gmake -C bsnes local=false - gmake -C bsnes local=false
@ -16,7 +39,45 @@ freebsd-x86_64-binaries_task:
- cp -a bsnes/Database/* bsnes-nightly/Database - cp -a bsnes/Database/* bsnes-nightly/Database
- cp -a shaders bsnes-nightly/Shaders - cp -a shaders bsnes-nightly/Shaders
- cp -a GPLv3.txt bsnes-nightly - cp -a GPLv3.txt bsnes-nightly
- cp -a extras/* bsnes-nightly - zip -r bsnes-nightly.zip bsnes-nightly
bsnes-nightly_artifacts:
path: "bsnes-nightly.zip"
windows-x86_64-binaries_task:
container:
image: ubuntu:latest
setup_script:
- apt-get update && apt-get -y install build-essential mingw-w64 zip
compile_script:
- make -C bsnes local=false platform=windows compiler="x86_64-w64-mingw32-g++" windres="x86_64-w64-mingw32-windres"
package_script:
- mkdir bsnes-nightly
- mkdir bsnes-nightly/Database
- mkdir bsnes-nightly/Firmware
- cp -a bsnes/out/bsnes bsnes-nightly/bsnes.exe
- cp -a bsnes/Database/* bsnes-nightly/Database
- cp -a shaders bsnes-nightly/Shaders
- cp -a GPLv3.txt bsnes-nightly
- zip -r bsnes-nightly.zip bsnes-nightly
bsnes-nightly_artifacts:
path: "bsnes-nightly.zip"
macOS-x86_64-binaries_task:
osx_instance:
image: mojave-base
compile_script:
- make -C bsnes local=false
package_script:
- mkdir bsnes-nightly
- cp -a bsnes/out/bsnes.app bsnes-nightly
- cp -a GPLv3.txt bsnes-nightly
- zip -r bsnes-nightly.zip bsnes-nightly - zip -r bsnes-nightly.zip bsnes-nightly
bsnes-nightly_artifacts: bsnes-nightly_artifacts:

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
patreon: byuu

View File

@ -1,139 +0,0 @@
name: Build
on:
push:
branches: [ master ]
tags: [ 'v*' ]
pull_request:
branches: [ master ]
permissions:
contents: write
jobs:
build:
strategy:
matrix:
os:
- name: ubuntu
version: latest
- name: windows
version: latest
- name: macos
version: latest
runs-on: ${{ matrix.os.name }}-${{ matrix.os.version }}
steps:
- uses: actions/checkout@v4
- name: Install Dependencies
if: matrix.os.name == 'ubuntu'
run: |
sudo apt-get update -y -qq
sudo apt-get install libsdl2-dev libgtk-3-dev gtksourceview-3.0 libao-dev libopenal-dev
- name: Make
run: make -j4 -C bsnes local=false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: bsnes-${{ matrix.os.name }}
path: bsnes/out/bsnes*
release:
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/')
# Prevent multiple conflicting release jobs from running at once.
concurrency: release-${{ github.ref == 'refs/heads/master' && 'nightly' || github.ref }}
runs-on: ubuntu-latest
needs:
- build
steps:
- uses: actions/checkout@v4
with:
path: 'src'
- name: Download Artifacts
uses: actions/download-artifact@v4.1.7
with:
path: 'bin'
- name: Package Artifacts
run: |
set -eu
case ${GITHUB_REF} in
refs/tags/*) suffix="-${GITHUB_REF#refs/tags/}" ;;
refs/heads/master) suffix="-nightly" ;;
*) suffix="" ;;
esac
srcdir="${GITHUB_WORKSPACE}/src"
bindir="${GITHUB_WORKSPACE}/bin"
# Hack: Workaround for GitHub artifacts losing attributes.
for program in bsnes
do
chmod +x ${bindir}/${program}-ubuntu/${program}
chmod +x ${bindir}/${program}-macos/${program}.app/Contents/MacOS/${program}
done
for os in ubuntu windows macos
do
mkdir "${os}"
cd "${os}"
# Package bsnes.
outdir=bsnes${suffix}
mkdir ${outdir}
mkdir ${outdir}/Database
mkdir ${outdir}/Firmware
cp -ar ${bindir}/bsnes-${os}/* ${outdir}
cp -a ${srcdir}/bsnes/Database/* ${outdir}/Database
cp -a ${srcdir}/shaders ${outdir}/Shaders
cp -a ${srcdir}/GPLv3.txt ${outdir}
cp -a ${srcdir}/extras/* ${outdir}
zip -r ../bsnes-${os}.zip ${outdir}
cd -
done
- name: Calculate release info
id: relinfo
run: |
echo "datetime=$(date -u +"%Y-%m-%d %T %Z")" >> $GITHUB_OUTPUT
echo "date=$(date +"%Y-%m-%d")" >> $GITHUB_OUTPUT
- name: Delete old nightly release
uses: actions/github-script@v7
id: release
if: ${{!startsWith(github.ref, 'refs/tags/')}}
with:
retries: 3
script: |
const {owner, repo} = context.repo;
try {
const release = await github.rest.repos.getReleaseByTag({owner, repo, tag: "nightly"});
if (release && release.status === 200) {
await github.rest.repos.deleteRelease({owner, repo, release_id: release.data.id});
}
} catch (e) {
console.log(`error deleting old release: ${e}`);
}
try {
await github.rest.git.deleteRef({owner, repo, ref: "tags/nightly"});
} catch (e) {
console.log(`error trying to delete ref: ${e}`);
}
await github.rest.git.createTag({
owner,
repo,
tag: "nightly",
message: "nightly release",
object: context.sha,
type: "commit",
})
- name: Create nightly release
uses: softprops/action-gh-release@v1
if: ${{!startsWith(github.ref, 'refs/tags/')}}
with:
tag_name: nightly
name: bsnes nightly ${{steps.relinfo.outputs.date}}
body: Auto-generated nightly release on ${{steps.relinfo.outputs.datetime}}
files: bsnes*.zip
prerelease: true
- name: Create version release
uses: softprops/action-gh-release@v1
if: ${{startsWith(github.ref, 'refs/tags/')}}
with:
name: bsnes ${{github.ref_name}}
body: This is bsnes ${{github.ref_name}}, released on ${{steps.relinfo.outputs.date}}.
files: bsnes*.zip

1
.gitignore vendored
View File

@ -1 +0,0 @@
hiro/qt/qt.moc

89
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,89 @@
Contributing
============
Code contributions are most welcome and highly appreciated!
But first, please note that although bsnes is licensed under the GPLv3 license,
in order to be merged upstream, any code contributions must be provided under
the ISC source code license.
This is *not* a CLA (community license agreement), no legal contract needs to be
signed, and you will maintain full and exclusive copyright ownership over any
contributed source code.
There are two reasons for this requirement:
GPLv4+
------
bsnes is currently licensed under the GPLv3 license only. I do not license bsnes
under the GPLv3 or later license, because there is no way of knowing what the
GPLv4 and later licenses will change, and if they will be in the best interests
of emulator development and video game preservation.
Although I put a good deal of trust into the FSF, no one is an oracle that can
predict the future. Would *you* agree to a license before being able to read it?
However, the GPLv4 may prove beneficial, and close important holes in the GPLv3
license, just as the GPLv3 license closed the GPLv2's TiVoization loophole. And
so it is important that bsnes retains the option of relicensing to the GPLv4+ in
the future.
As a point of interest, there have been projects with similar concerns about
using a GPLv2 or later clause, that are now permanently stuck on the GPLv2
license. There have also been projects that did use a GPLv2 or later clause,
only to disagree with the changes introduced in the GPLv3.
ISC
---
The more important reason for this requirement is that it is my intention to
release the entirety of bsnes under the ISC license once official upstream
development has ceased.
The reason I would want to relicense bsnes to the ISC license upon its official
discontinuation is because once again, no one is an oracle, and I cannot predict
what future issues bsnes permanently remaining under the GPLv3 license may
cause.
For instance, imagine a world where a certain vendor took over the world, and
the only way to distribute applications was with their approval, and their store
rules forbade GPLv3 software. Or perhaps a world where the GPL was abandoned in
favor of the new OSSv1 license. But GPLv3 software was incompatible with the
OSSv1 license. Other open source developers would not be able to use bsnes in
that scenario.
It would be very disappointing if all of our work ended up unusable 50+ years
into the future because it was permanently bound to the GPLv3 license.
GPLv3
-----
The reason I use the GPLv3 license currently is because it is a balance between
altruism and self-interest. The GPLv3 allows other vendors to sell my own code
without sharing revenue with me, and indeed this has already happened. But the
GPLv3 also prevents other vendors from improving upon bsnes without sharing
their work with everyone else as I have.
While I am actively developing bsnes, I do not wish to compete against myself.
As such, I believe the GPLv3 is the best license during active development, and
the ISC is the best license once bsnes is officially discontinued.
Considerations
--------------
This is the part that should concern you as a contributor: I am not requesting
contributed source code to be released under the ISC so that I personally may
sell GPLv3 commercial license exemptions to your work, but in the future when
bsnes is released under the ISC license, that will open the door for anyone to
sell the work commercially in a closed source form.
If this is not acceptable to you, I wholly understand and I welcome you to
release your work under the GPLv3 in the form of a bsnes fork. And if your work
is not an essential part of the core emulation -- that is to say, it may be
optionally disabled -- then I am still willing to work with you in merging such
work upstream anyway under the full GPLv3 license, but please reach out to me
first before developing under the assumption your work will be merged upstream.
Thank you very much for reading and hopefully for your understanding.

View File

@ -1,59 +0,0 @@
bsnes was originally developed by byuu, and is now a group project.
This software would not be where it is today without the help and support of the following individuals:
- Andreas Naive
- Ange Albertini
- anomie
- AWJ
- BearOso
- Bisqwit
- blargg
- byuu
- Łukasz Krawczyk
- Danish
- DerKoun
- DMV27
- Dr. Decapitator
- endrift
- Fatbag
- FitzRoy
- gekkio
- GIGO
- Hendricks266
- hex_usr
- ikari_01
- jchadwick
- Jonas Quinn
- kode54
- krom
- Lioncash
- Lord Nightmare
- lowkey
- Matthew Callis
- Max833
- MerryMage
- mightymo
- Nach
- ncbncb
- neviksti
- OV2
- Overload
- p4plus2
- quequotion
- qwertymodo
- RedDwarf
- Richard Bannister
- Ryphecha
- segher
- Sintendo
- SuperMikeMan
- Talarubi
- tetsuo55
- TmEE
- TRAC
- wareya
- zones
If you feel you are missing from this list, please submit a pull request and we will be happy to add your name here!

View File

@ -1,14 +1,14 @@
---------------------------------------------------------------------- ----------------------------------------------------------------------
bsnes - Super Nintendo emulator bsnes - Super Nintendo emulator
Copyright © 2004-2020 byuu et al Copyright © 2004-2019 byuu
https://byuu.org/bsnes/ https://byuu.org
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, specifically version 3 of the License
(at your option) any later version. and no other version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
@ -25,7 +25,7 @@ nall - C++ template library
ruby - Hardware abstraction library ruby - Hardware abstraction library
hiro - User interface library hiro - User interface library
Copyright © 2006-2020 byuu et al Copyright © 2006-2019 byuu
Permission to use, copy, modify, and/or distribute this software for Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the any purpose with or without fee is hereby granted, provided that the

View File

@ -1,15 +1,31 @@
bsnes bsnes
===== =====
![bsnes logo](bsnes/target-bsnes/resource/logo.png) ![bsnes logo © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-logo.png)
bsnes is a multi-platform Super Nintendo (Super Famicom) emulator, originally bsnes is a multi-platform Super Nintendo (Super Famicom) emulator from
developed by Near, which focuses on performance, [byuu](https://byuu.org) that focuses on performance, features, and ease of use.
features, and ease of use.
bsnes currently enjoys 100% known, bug-free compatibility with the entire SNES
library when configured to its most accurate settings, giving it the same
accuracy level as higan. Accuracy can also optionally be traded for performance,
allowing bsnes to operate more than 300% faster than higan while still remaining
almost as accurate.
Development
-----------
Git is used for the development of new releases, and represents a staging
environment. As bsnes is rather mature, things should generally be quite stable.
However, bugs will exist, regressions will occur, so proceed at your own risk.
If stability is required, please download the latest stable release from the
[official website.](https://bsnes.byuu.org)
Unique Features Unique Features
--------------- ---------------
- 100% (known) bug-free compatibility with the entire officially licensed SNES games library
- True Super Game Boy emulation (using the SameBoy core by Lior Halphon) - True Super Game Boy emulation (using the SameBoy core by Lior Halphon)
- HD mode 7 graphics with optional supersampling (by DerKoun) - HD mode 7 graphics with optional supersampling (by DerKoun)
- Low-level emulation of all SNES coprocessors (DSP-n, ST-01n, Cx4) - Low-level emulation of all SNES coprocessors (DSP-n, ST-01n, Cx4)
@ -56,20 +72,28 @@ Standard Features
Links Links
----- -----
- [Official git repository](https://github.com/bsnes-emu/bsnes) - [Official website](https://byuu.org/bsnes)
- [Official Discord](https://discord.gg/B27hf27ZVf) - [Official git repository](https://github.com/byuu/bsnes)
- [Developer resources](https://byuu.net)
- [Donations](https://patreon.com/byuu)
Release Builds
--------------
- [Windows binaries](https://byuu.itch.io/bsnes)
Nightly Builds Nightly Builds
-------------- --------------
- [Windows](https://github.com/bsnes-emu/bsnes/releases/download/nightly/bsnes-windows.zip) - [Download](https://cirrus-ci.com/github/byuu/bsnes/master)
- [macOS](https://github.com/bsnes-emu/bsnes/releases/download/nightly/bsnes-macos.zip) - ![Build status](https://api.cirrus-ci.com/github/byuu/bsnes.svg?task=windows-x86_64-binaries)
- [Linux](https://github.com/bsnes-emu/bsnes/releases/download/nightly/bsnes-ubuntu.zip) - ![Build status](https://api.cirrus-ci.com/github/byuu/bsnes.svg?task=macOS-x86_64-binaries)
- [FreeBSD](https://api.cirrus-ci.com/v1/artifact/github/bsnes-emu/bsnes/freebsd-x86_64-binaries/bsnes-nightly/bsnes-nightly.zip) - ![Build status](https://api.cirrus-ci.com/github/byuu/bsnes.svg?task=linux-x86_64-binaries)
- ![Build status](https://api.cirrus-ci.com/github/byuu/bsnes.svg?task=freebsd-x86_64-binaries)
Preview Preview
------- -------
![bsnes user interface](.assets/user-interface.png) ![bsnes user interface © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-user-interface.png)
![bsnes running Bahamut Lagoon](.assets/bahamut-lagoon.png) ![bsnes running Bahamut Lagoon © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-bahamut-lagoon.png)
![bsnes running Tengai Makyou Zero](.assets/tengai-makyou-zero.png) ![bsnes running Tengai Makyou Zero © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-tengai-makyou-zero.png)

View File

@ -1,5 +1,5 @@
database database
revision: 2020-01-21 revision: 2020-01-01
//Prototypes (JPN) //Prototypes (JPN)
@ -125,7 +125,7 @@ game
//Super Famicom (JPN) //Super Famicom (JPN)
database database
revision: 2020-01-21 revision: 2020-01-01
game game
sha256: 5c4e283efc338958b8dd45ebd6daf133a9eb280420a98e2e1df358ae0242c366 sha256: 5c4e283efc338958b8dd45ebd6daf133a9eb280420a98e2e1df358ae0242c366
@ -171,22 +171,6 @@ game
size: 0x8000 size: 0x8000
content: Save content: Save
game
sha256: a98eb5f0521746e6ce6d208591e86d366b6e0479d96474bfff43856fe8cfec12
label: バハムートラグーン
name: Bahamut Lagoon
region: SHVC-AXBJ-JPN
revision: SHVC-AXBJ-0
board: SHVC-1J3M-20
memory
type: ROM
size: 0x300000
content: Program
memory
type: RAM
size: 0x2000
content: Save
game game
sha256: 47779500c43a0c2e75d7684078489a17baea31170a123063b8ece6ce77359413 sha256: 47779500c43a0c2e75d7684078489a17baea31170a123063b8ece6ce77359413
label: ボール・ブレット・ガン label: ボール・ブレット・ガン
@ -1481,22 +1465,6 @@ game
size: 0x2000 size: 0x2000
content: Save content: Save
game
sha256: 77b2d5450ce3c87185f913c2584673530c13dfbe8cc433b1e9fe5e9a653bf7d5
label: テイルズオブファンタジア
name: Tales of Phantasia
region: SHVC-ATVJ-JPN
revision: SHVC-ATVJ-0
board: SHVC-LJ3M-01
memory
type: ROM
size: 0x600000
content: Program
memory
type: RAM
size: 0x2000
content: Save
game game
sha256: c9002e77bcc656e033c35e2574ee6067c4c0d070943359a850806c123a558949 sha256: c9002e77bcc656e033c35e2574ee6067c4c0d070943359a850806c123a558949
label: 戦え原始人3 主役はやっぱりジョーアンドマック label: 戦え原始人3 主役はやっぱりジョーアンドマック

View File

@ -1,53 +0,0 @@
bsnes source code
=================
A guide to what all these directories are for:
- [Database](./Database/)
contains the databases bsnes uses
to figure out what circuit board a game expects,
and also the database of pre-made game cheats
- [emulator](./emulator/)
contains the interface
that the emulation core in [sfc](./sfc/) implements
- It comes from higan v106,
which has many emulation cores
that all implement the same interface —
bsnes only has one,
but the interface is still a good abstraction,
so it's still here.
- [filter](./filter/)
contains classic CPU-based image upscaling filters,
like HQ2x and Super Eagle
- [gb](./gb/)
contains the SameBoy emulator code
used for Super Game Boy emulation
- [heuristics](./heuristics/)
contains the logic that guesses
which memory map a particular game expects
and what extra hardware it assumes is present,
when a game cannot be found in the database
- [Locale](./Locale/)
contains translation databases
that bsnes can use to display its user interface
in a different language
- [lzma](./lzma/)
contains the 7-Zip SDK
allowing bsnes to understad `.7z` archives
- [processor](./processor/)
contains all the CPU emulation cores
used by the hardware bsnes emulates
- Another holdover from higan v106,
where different supported systems
happen to use the same model CPU,
so the CPU emulation cores
are not specific to a system
- [sfc](./sfc/)
contains Super Famicom (SNES) emulation code
- [target-bsnes](./target-bsnes/)
contains the normal bsnes user interface
- [target-libretro](./target-libretro/)
implements the "libretro" API
on top of bsnes' native
[emulator](./emulator/) API,
so bsnes can be used with libretro-based frontends

View File

@ -1,3 +1,5 @@
#define double float
namespace Emulator { namespace Emulator {
#include "stream.cpp" #include "stream.cpp"

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#define double float
#include <nall/dsp/iir/dc-removal.hpp> #include <nall/dsp/iir/dc-removal.hpp>
#include <nall/dsp/iir/one-pole.hpp> #include <nall/dsp/iir/one-pole.hpp>

View File

@ -29,13 +29,13 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "bsnes"; static const string Name = "bsnes";
static const string Version = "115"; static const string Version = "114";
static const string Copyright = "bsnes team"; static const string Author = "byuu";
static const string License = "GPLv3 or later"; static const string License = "GPLv3";
static const string Website = "https://bsnes.dev"; static const string Website = "https://byuu.org";
//incremented only when serialization format changes //incremented only when serialization format changes
static const string SerializerVersion = "115"; static const string SerializerVersion = "112";
namespace Constants { namespace Constants {
namespace Colorburst { namespace Colorburst {

View File

@ -17,7 +17,7 @@ struct Platform {
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> shared_pointer<vfs::file> { return {}; } virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> shared_pointer<vfs::file> { return {}; }
virtual auto load(uint id, string name, string type, vector<string> options = {}) -> Load { return {}; } virtual auto load(uint id, string name, string type, vector<string> options = {}) -> Load { return {}; }
virtual auto videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void {} virtual auto videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void {}
virtual auto audioFrame(const double* samples, uint channels) -> void {} virtual auto audioFrame(const float* samples, uint channels) -> void {}
virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; } virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {} virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}
virtual auto dipSettings(Markup::Node node) -> uint { return 0; } virtual auto dipSettings(Markup::Node node) -> uint { return 0; }

View File

@ -1,10 +0,0 @@
# Always use LF line endings for shaders
*.fsh text eol=lf
*.metal text eol=lf
HexFiend/* linguist-vendored
*.inc linguist-language=C
Core/*.h linguist-language=C
SDL/*.h linguist-language=C
Windows/*.h linguist-language=C
Cocoa/*.h linguist-language=Objective-C

View File

@ -1,25 +0,0 @@
Blargg's Test ROMs by Shay Green <gblargg@gmail.com>
Acid2 tests by Matt Currie under MIT:
MIT License
Copyright (c) 2020 Matt Currie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,23 +0,0 @@
case `echo $1 | cut -d '-' -f 1` in
ubuntu)
sudo apt-get -qq update
sudo apt-get install -yq bison libpng-dev pkg-config libsdl2-dev
(
cd `mktemp -d`
curl -L https://github.com/rednex/rgbds/archive/v0.4.0.zip > rgbds.zip
unzip rgbds.zip
cd rgbds-*
make -sj
sudo make install
cd ..
rm -rf *
)
;;
macos)
brew install rgbds sdl2
;;
*)
echo "Unsupported OS"
exit 1
;;
esac

Binary file not shown.

View File

@ -1,33 +0,0 @@
set -e
./build/bin/tester/sameboy_tester --jobs 5 \
--length 45 .github/actions/cgb_sound.gb \
--length 10 .github/actions/cgb-acid2.gbc \
--length 10 .github/actions/dmg-acid2.gb \
--dmg --length 45 .github/actions/dmg_sound-2.gb \
--dmg --length 20 .github/actions/oam_bug-2.gb
mv .github/actions/dmg{,-mode}-acid2.bmp
./build/bin/tester/sameboy_tester \
--dmg --length 10 .github/actions/dmg-acid2.gb
set +e
FAILED_TESTS=`
shasum .github/actions/*.bmp | grep -E -v \(\
5283564df0cf5bb78a7a90aff026c1a4692fd39e\ \ .github/actions/cgb-acid2.bmp\|\
dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\
0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\
a732077f98f43d9231453b1764d9f797a836924d\ \ .github/actions/dmg-mode-acid2.bmp\|\
c9e944b7e01078bdeba1819bc2fa9372b111f52d\ \ .github/actions/dmg_sound-2.bmp\|\
f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\
\)`
if [ -n "$FAILED_TESTS" ] ; then
echo "Failed the following tests:"
echo $FAILED_TESTS | tr " " "\n" | grep -o -E "[^/]+\.bmp" | sed s/.bmp// | sort
exit 1
fi
echo Passed all tests

View File

@ -1,36 +0,0 @@
name: "Bulidability and Sanity"
on: push
jobs:
sanity:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, ubuntu-18.04]
cc: [gcc, clang]
include:
- os: macos-latest
cc: clang
extra_target: cocoa
exclude:
- os: macos-latest
cc: gcc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Install deps
shell: bash
run: |
./.github/actions/install_deps.sh ${{ matrix.os }}
- name: Build
run: |
${{ matrix.cc }} -v; (make -j sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} || (echo "==== Build Failed ==="; make sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }}))
- name: Sanity tests
shell: bash
run: |
./.github/actions/sanity_tests.sh
- name: Upload binaries
uses: actions/upload-artifact@v1
with:
name: sameboy-canary-${{ matrix.os }}-${{ matrix.cc }}
path: build/bin

1
bsnes/gb/.gitignore vendored
View File

@ -1 +0,0 @@
build

View File

@ -1,217 +0,0 @@
# BESS Best Effort Save State 1.0
## Motivation
BESS is a save state format specification designed to allow different emulators, as well as majorly different versions of the same emulator, to import save states from other BESS-compliant save states. BESS works by appending additional, implementation-agnostic information about the emulation state. This allows a single save state file to be read as both a fully-featured, implementation specific save state which includes detailed timing information; and as a portable "best effort" save state that represents a state accurately enough to be restored in casual use-cases.
## Specification
Every integer used in the BESS specification is stored in Little Endian encoding.
### BESS footer
BESS works by appending a detectable footer at the end of an existing save state format. The footer uses the following format:
| Offset from end of file | Content |
|-------------------------|-------------------------------------------------------|
| -8 | Offset to the first BESS Block, from the file's start |
| -4 | The ASCII string 'BESS' |
### BESS blocks
BESS uses a block format where each block contains the following header:
| Offset | Content |
|--------|---------------------------------------|
| 0 | A four-letter ASCII identifier |
| 4 | Length of the block, excluding header |
Every block is followed by another block, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure).
#### NAME block
The NAME block uses the `'NAME'` identifier, and is an optional block that contains the name of the emulator that created this save state. While optional, it is highly recommended to be included in every implementation it allows the user to know which emulator and version is compatible with the native save state format contained in this file. When used, this block should come first.
The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII.
#### INFO block
The INFO block uses the `'INFO'` identifier, and is an optional block that contains information about the ROM this save state originates from. When used, this block should come before `CORE` but after `NAME`. This block is 0x12 bytes long, and it follows this structure:
| Offset | Content |
|--------|--------------------------------------------------|
| 0x00 | Bytes 0x134-0x143 from the ROM (Title) |
| 0x10 | Bytes 0x14E-0x14F from the ROM (Global checksum) |
#### CORE block
The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, unless the `NAME` or `INFO` blocks exist then it must come directly after them. An implementation should not enforce block order on blocks unknown to it for future compatibility.
The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows:
| Offset | Content |
|--------|----------------------------------------|
| 0x00 | Major BESS version as a 16-bit integer |
| 0x02 | Minor BESS version as a 16-bit integer |
Both major and minor versions should be 1. Implementations are expected to reject incompatible majors, but still attempt to read newer minor versions.
| Offset | Content |
|--------|----------------------------------------|
| 0x04 | A four-character ASCII model identifier |
BESS uses a four-character string to identify Game Boy models:
* The first letter represents mutually-incompatible families of models and is required. The allowed values are `'G'` for the original Game Boy family, `'S'` for the Super Game Boy family, and `'C'` for the Game Boy Color and Advance family.
* The second letter represents a specific model within the family, and is optional (If an implementation does not distinguish between specific models in a family, a space character may be used). The allowed values for family G are `'D'` for DMG and `'M'` for MGB; the allowed values for family S are `'N'` for NTSC, `'P'` for PAL, and `'2'` for SGB2; and the allowed values for family C are `'C'` for CGB, and `'A'` for the various GBA line models.
* The third letter represents a specific CPU revision within a model, and is optional (If an implementation does not distinguish between revisions, a space character may be used). The allowed values for model GD (DMG) are `'0'` and `'A'`, through `'C'`; the allowed values for model CC (CGB) are `'0'` and `'A'`, through `'E'`; the allowed values for model CA (AGB, AGS, GBP) are `'0'`, `'A'` and `'B'`; and for every other model this value must be a space character.
* The last character is used for padding and must be a space character.
For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `'CCE '` represent a CGB using CPU revision E.
| Offset | Content |
|--------|--------------------------------------------------------|
| 0x08 | The value of the PC register |
| 0x0A | The value of the AF register |
| 0x0C | The value of the BC register |
| 0x0E | The value of the DE register |
| 0x10 | The value of the HL register |
| 0x12 | The value of the SP register |
| 0x14 | The value of IME (0 or 1) |
| 0x15 | The value of the IE register |
| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) |
| 0x17 | Reserved, must be 0 |
| 0x18 | The values of every memory-mapped register (128 bytes) |
The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note:
* Unused registers have Don't-Care values which should be ignored
* Unused register bits have Don't-Care values which should be ignored
* If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode
* Bit 2 determines DMG mode. A value of 0x04 usually denotes DMG mode, while a value of `0x80` usually denotes CGB mode.
* Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect
* If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored.
* BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid.
* Implementations should not start a serial transfer when writing the value of SB
* Similarly, no value of NRx4 should trigger a sound pulse on save state load
* And similarly again, implementations should not trigger DMA transfers when writing the values of DMA or HDMA5
* The value store for DIV will be used to set the internal divisor to `DIV << 8`
* Implementation should apply care when ordering the write operations (For example, writes to NR52 must come before writes to the other APU registers)
| Offset | Content |
|--------|--------------------------------------------------------------------|
| 0x98 | The size of RAM (32-bit integer) |
| 0x9C | The offset of RAM from file start (32-bit integer) |
| 0xA0 | The size of VRAM (32-bit integer) |
| 0xA4 | The offset of VRAM from file start (32-bit integer) |
| 0xA8 | The size of MBC RAM (32-bit integer) |
| 0xAC | The offset of MBC RAM from file start (32-bit integer) |
| 0xB0 | The size of OAM (=0xA0, 32-bit integer) |
| 0xB4 | The offset of OAM from file start (32-bit integer) |
| 0xB8 | The size of HRAM (=0x7F, 32-bit integer) |
| 0xBC | The offset of HRAM from file start (32-bit integer) |
| 0xC0 | The size of background palettes (=0x40 or 0, 32-bit integer) |
| 0xC4 | The offset of background palettes from file start (32-bit integer) |
| 0xC8 | The size of object palettes (=0x40 or 0, 32-bit integer) |
| 0xCC | The offset of object palettes from file start (32-bit integer) |
The contents of large buffers are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. Background and object palette sizes must be 0 for models prior to Game Boy Color.
An implementation needs handle size mismatches gracefully. For example, if too large MBC RAM size is specified, the superfluous data should be ignored. On the other hand, if a too small VRAM size is specified (For example, if it's a save state from an emulator emulating a CGB in DMG mode, and it didn't save the second CGB VRAM bank), the implementation is expected to set that extra bank to all zeros.
#### XOAM block
The XOAM block uses the `'XOAM'` identifier, and is an optional block that contains the data of extra OAM (addresses `0xFEA0-0xFEFF`). This block length must be `0x60`. Implementations that do not emulate this extra range are free to ignore the excess bytes, and to not create this block.
#### MBC block
The MBC block uses the `'MBC '` identifier, and is an optional block that is only used when saving states of ROMs that use an MBC. The length of this block is variable and must be divisible by 3.
This block contains an MBC-specific number of 3-byte-long pairs that represent the values of each MBC register. For example, for MBC5 the contents would look like:
| Offset | Content |
|--------|---------------------------------------|
| 0x0 | The value 0x0000 as a 16-bit integer |
| 0x2 | 0x0A if RAM is enabled, 0 otherwise |
| 0x3 | The value 0x2000 as a 16-bit integer |
| 0x5 | The lower 8 bits of the ROM bank |
| 0x6 | The value 0x3000 as a 16-bit integer |
| 0x8 | The bit 9 of the ROM bank |
| 0x9 | The value 0x4000 as a 16-bit integer |
| 0xB | The current RAM bank |
An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. Implementations must perform the writes in order (i.e. not reverse, sorted, or any other transformation on their order)
#### RTC block
The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB.
The length of this block is 0x30 bytes long and it follows the following structure:
| Offset | Content |
|--------|------------------------------------------------------------------------|
| 0x00 | Current seconds (1 byte), followed by 3 bytes of padding |
| 0x04 | Current minutes (1 byte), followed by 3 bytes of padding |
| 0x08 | Current hours (1 byte), followed by 3 bytes of padding |
| 0x0C | Current days (1 byte), followed by 3 bytes of padding |
| 0x10 | Current high/overflow/running (1 byte), followed by 3 bytes of padding |
| 0x14 | Latched seconds (1 byte), followed by 3 bytes of padding |
| 0x18 | Latched minutes (1 byte), followed by 3 bytes of padding |
| 0x1C | Latched hours (1 byte), followed by 3 bytes of padding |
| 0x20 | Latched days (1 byte), followed by 3 bytes of padding |
| 0x24 | Latched high/overflow/running (1 byte), followed by 3 bytes of padding |
| 0x28 | UNIX timestamp at the time of the save state (64-bit) |
#### HUC3 block
The HUC3 block uses the `'HUC3'` identifier, and is an optional block that is used while emulating an HuC3 cartridge to store RTC and alarm information. The contents of this block are identical to HuC3 RTC saves from SameBoy.
The length of this block is 0x11 bytes long and it follows the following structure:
| Offset | Content |
|--------|-------------------------------------------------------|
| 0x00 | UNIX timestamp at the time of the save state (64-bit) |
| 0x08 | RTC minutes (16-bit) |
| 0x0A | RTC days (16-bit) |
| 0x0C | Scheduled alarm time minutes (16-bit) |
| 0x0E | Scheduled alarm time days (16-bit) |
| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) |
#### SGB block
The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing.
The length of this block is 0x39 bytes, but implementations should allow and ignore excess data in this block for extensions. The block follows the following structure:
| Offset | Content |
|--------|--------------------------------------------------------------------------------------------------------------------------|
| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) |
| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) |
| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) |
| 0x0C | The offset of the border tilemap (LE 16-bit sequences, 32-bit integer) |
| 0x10 | The size of the border palettes (=0x80, 32-bit integer) |
| 0x14 | The offset of the border palettes (LE 16-bit sequences, 32-bit integer) |
| 0x18 | The size of active colorization palettes (=0x20, 32-bit integer) |
| 0x1C | The offset of the active colorization palettes (LE 16-bit sequences, 32-bit integer) |
| 0x20 | The size of RAM colorization palettes (=0x1000, 32-bit integer) |
| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) |
| 0x28 | The size of the attribute map (=0x168, 32-bit integer) |
| 0x2C | The offset of the attribute map (32-bit integer) |
| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) |
| 0x34 | The offset of the attribute files (32-bit integer) |
| 0x38 | Multiplayer status (1 byte); high nibble is player count (1, 2 or 4), low nibble is current player (Where Player 1 is 0) |
If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default.
#### END block
The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. The length of the END block must be 0.
## Validation and Failures
Other than previously specified required fail conditions, an implementation is free to decide what format errors should abort the loading of a save file. Structural errors (e.g. a block with an invalid length, a file offset that is outside the file's range, or a missing END block) should be considered as irrecoverable errors. Other errors that are considered fatal by SameBoy's implementation:
* Duplicate CORE block
* A known block, other than NAME, appearing before CORE
* An invalid length for the XOAM, RTC, SGB or HUC3 blocks
* An invalid length of MBC (not a multiple of 3)
* A write outside the $0000-$7FFF and $A000-$BFFF ranges in the MBC block
* An SGB block on a save state targeting another model
* An END block with non-zero length

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 B

View File

@ -1,2 +0,0 @@
AGB EQU 1
include "cgb_boot.asm"

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
FAST EQU 1
include "cgb_boot.asm"

View File

@ -1,177 +0,0 @@
; SameBoy DMG bootstrap ROM
; Todo: use friendly names for HW registers instead of magic numbers
SECTION "BootCode", ROM0[$0]
Start:
; Init stack pointer
ld sp, $fffe
; Clear memory VRAM
ld hl, $8000
.clearVRAMLoop
ldi [hl], a
bit 5, h
jr z, .clearVRAMLoop
; Init Audio
ld a, $80
ldh [$26], a
ldh [$11], a
ld a, $f3
ldh [$12], a
ldh [$25], a
ld a, $77
ldh [$24], a
; Init BG palette
ld a, $54
ldh [$47], a
; Load logo from ROM.
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
; Tiles are ordered left to right, top to bottom.
ld de, $104 ; Logo start
ld hl, $8010 ; This is where we load the tiles in VRAM
.loadLogoLoop
ld a, [de] ; Read 2 rows
ld b, a
call DoubleBitsAndWriteRow
call DoubleBitsAndWriteRow
inc de
ld a, e
xor $34 ; End of logo
jr nz, .loadLogoLoop
; Load trademark symbol
ld de, TrademarkSymbol
ld c,$08
.loadTrademarkSymbolLoop:
ld a,[de]
inc de
ldi [hl],a
inc hl
dec c
jr nz, .loadTrademarkSymbolLoop
; Set up tilemap
ld a,$19 ; Trademark symbol
ld [$9910], a ; ... put in the superscript position
ld hl,$992f ; Bottom right corner of the logo
ld c,$c ; Tiles in a logo row
.tilemapLoop
dec a
jr z, .tilemapDone
ldd [hl], a
dec c
jr nz, .tilemapLoop
ld l,$0f ; Jump to top row
jr .tilemapLoop
.tilemapDone
ld a, 30
ldh [$ff42], a
; Turn on LCD
ld a, $91
ldh [$40], a
ld d, (-119) & $FF
ld c, 15
.animate
call WaitFrame
ld a, d
sra a
sra a
ldh [$ff42], a
ld a, d
add c
ld d, a
ld a, c
cp 8
jr nz, .noPaletteChange
ld a, $A8
ldh [$47], a
.noPaletteChange
dec c
jr nz, .animate
ld a, $fc
ldh [$47], a
; Play first sound
ld a, $83
call PlaySound
ld b, 5
call WaitBFrames
; Play second sound
ld a, $c1
call PlaySound
; Wait ~1 second
ld b, 60
call WaitBFrames
; Set registers to match the original DMG boot
ld hl, $01B0
push hl
pop af
ld hl, $014D
ld bc, $0013
ld de, $00D8
; Boot the game
jp BootGame
DoubleBitsAndWriteRow:
; Double the most significant 4 bits, b is shifted by 4
ld a, 4
ld c, 0
.doubleCurrentBit
sla b
push af
rl c
pop af
rl c
dec a
jr nz, .doubleCurrentBit
ld a, c
; Write as two rows
ldi [hl], a
inc hl
ldi [hl], a
inc hl
ret
WaitFrame:
push hl
ld hl, $FF0F
res 0, [hl]
.wait
bit 0, [hl]
jr z, .wait
pop hl
ret
WaitBFrames:
call WaitFrame
dec b
jr nz, WaitBFrames
ret
PlaySound:
ldh [$13], a
ld a, $87
ldh [$14], a
ret
TrademarkSymbol:
db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
SECTION "BootGame", ROM0[$fe]
BootGame:
ldh [$50], a

View File

@ -1,102 +0,0 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
void opts(uint8_t byte, uint8_t *options)
{
*(options++) = byte | ((byte << 1) & 0xff);
*(options++) = byte & (byte << 1);
*(options++) = byte | ((byte >> 1) & 0xff);
*(options++) = byte & (byte >> 1);
}
void write_all(int fd, const void *buf, size_t count) {
while (count) {
ssize_t written = write(fd, buf, count);
if (written < 0) {
fprintf(stderr, "write");
exit(1);
}
count -= written;
buf += written;
}
}
int main()
{
static uint8_t source[0x4000];
size_t size = read(STDIN_FILENO, &source, sizeof(source));
unsigned pos = 0;
assert(size <= 0x4000);
while (size && source[size - 1] == 0) {
size--;
}
uint8_t literals[8];
size_t literals_size = 0;
unsigned bits = 0;
unsigned control = 0;
unsigned prev[2] = {-1, -1}; // Unsigned to allow "not set" values
while (true) {
uint8_t byte = 0;
if (pos == size){
if (bits == 0) break;
}
else {
byte = source[pos++];
}
if (byte == prev[0] || byte == prev[1]) {
bits += 2;
control <<= 1;
control |= 1;
control <<= 1;
if (byte == prev[1]) {
control |= 1;
}
}
else {
bits += 2;
control <<= 2;
uint8_t options[4];
opts(prev[1], options);
bool found = false;
for (unsigned i = 0; i < 4; i++) {
if (options[i] == byte) {
// 01 = modify
control |= 1;
bits += 2;
control <<= 2;
control |= i;
found = true;
break;
}
}
if (!found) {
literals[literals_size++] = byte;
}
}
prev[0] = prev[1];
prev[1] = byte;
if (bits >= 8) {
uint8_t outctl = control >> (bits - 8);
assert(outctl != 1);
write_all(STDOUT_FILENO, &outctl, 1);
write_all(STDOUT_FILENO, literals, literals_size);
bits -= 8;
control &= (1 << bits) - 1;
literals_size = 0;
}
}
uint8_t end_byte = 1;
write_all(STDOUT_FILENO, &end_byte, 1);
return 0;
}

View File

@ -1,2 +0,0 @@
SGB2 EQU 1
include "sgb_boot.asm"

View File

@ -1,213 +0,0 @@
; SameBoy SGB bootstrap ROM
; Todo: use friendly names for HW registers instead of magic numbers
SECTION "BootCode", ROM0[$0]
Start:
; Init stack pointer
ld sp, $fffe
; Clear memory VRAM
ld hl, $8000
.clearVRAMLoop
ldi [hl], a
bit 5, h
jr z, .clearVRAMLoop
; Init Audio
ld a, $80
ldh [$26], a
ldh [$11], a
ld a, $f3
ldh [$12], a
ldh [$25], a
ld a, $77
ldh [$24], a
; Init BG palette to white
ld a, $0
ldh [$47], a
; Load logo from ROM.
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
; Tiles are ordered left to right, top to bottom.
ld de, $104 ; Logo start
ld hl, $8010 ; This is where we load the tiles in VRAM
.loadLogoLoop
ld a, [de] ; Read 2 rows
ld b, a
call DoubleBitsAndWriteRow
call DoubleBitsAndWriteRow
inc de
ld a, e
xor $34 ; End of logo
jr nz, .loadLogoLoop
; Load trademark symbol
ld de, TrademarkSymbol
ld c,$08
.loadTrademarkSymbolLoop:
ld a,[de]
inc de
ldi [hl],a
inc hl
dec c
jr nz, .loadTrademarkSymbolLoop
; Set up tilemap
ld a,$19 ; Trademark symbol
ld [$9910], a ; ... put in the superscript position
ld hl,$992f ; Bottom right corner of the logo
ld c,$c ; Tiles in a logo row
.tilemapLoop
dec a
jr z, .tilemapDone
ldd [hl], a
dec c
jr nz, .tilemapLoop
ld l,$0f ; Jump to top row
jr .tilemapLoop
.tilemapDone
; Turn on LCD
ld a, $91
ldh [$40], a
ld a, $f1 ; Packet magic, increases by 2 for every packet
ldh [$80], a
ld hl, $104 ; Header start
xor a
ld c, a ; JOYP
.sendCommand
xor a
ld [c], a
ld a, $30
ld [c], a
ldh a, [$80]
call SendByte
push hl
ld b, $e
ld d, 0
.checksumLoop
call ReadHeaderByte
add d
ld d, a
dec b
jr nz, .checksumLoop
; Send checksum
call SendByte
pop hl
ld b, $e
.sendLoop
call ReadHeaderByte
call SendByte
dec b
jr nz, .sendLoop
; Done bit
ld a, $20
ld [c], a
ld a, $30
ld [c], a
; Update command
ldh a, [$80]
add 2
ldh [$80], a
ld a, $58
cp l
jr nz, .sendCommand
; Write to sound registers for DMG compatibility
ld c, $13
ld a, $c1
ld [c], a
inc c
ld a, 7
ld [c], a
; Init BG palette
ld a, $fc
ldh [$47], a
; Set registers to match the original SGB boot
IF DEF(SGB2)
ld a, $FF
ELSE
ld a, 1
ENDC
ld hl, $c060
; Boot the game
jp BootGame
ReadHeaderByte:
ld a, $4F
cp l
jr c, .zero
ld a, [hli]
ret
.zero:
inc hl
xor a
ret
SendByte:
ld e, a
ld d, 8
.loop
ld a, $10
rr e
jr c, .zeroBit
add a ; 10 -> 20
.zeroBit
ld [c], a
ld a, $30
ld [c], a
dec d
ret z
jr .loop
DoubleBitsAndWriteRow:
; Double the most significant 4 bits, b is shifted by 4
ld a, 4
ld c, 0
.doubleCurrentBit
sla b
push af
rl c
pop af
rl c
dec a
jr nz, .doubleCurrentBit
ld a, c
; Write as two rows
ldi [hl], a
inc hl
ldi [hl], a
inc hl
ret
WaitFrame:
push hl
ld hl, $FF0F
res 0, [hl]
.wait
bit 0, [hl]
jr z, .wait
pop hl
ret
TrademarkSymbol:
db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
SECTION "BootGame", ROM0[$fe]
BootGame:
ldh [$50], a

View File

@ -1 +0,0 @@
See https://sameboy.github.io/changelog/

View File

@ -1,79 +0,0 @@
# SameBoy Coding and Contribution Guidelines
## Issues
GitHub Issues are the most effective way to report a bug or request a feature in SameBoy. When reporting a bug, make sure you use the latest stable release, and make sure you mention the SameBoy frontend (Cocoa, SDL, Libretro) and operating system you're using. If you're using Linux/BSD/etc, or you build your own copy of SameBoy for another reason, give as much details as possible on your environment.
If your bug involves a crash, please attach a crash log or a core dump. If you're using Linux/BSD/etc, or if you're using the Libretro core, please attach the `sameboy` binary (or `libretro_sameboy` library) in that case.
If your bug is a regression, it'd be extremely helpful if you can report the the first affected version. You get extra credits if you use `git bisect` to point the exact breaking commit.
If your bug is an emulation bug (Such as a failing test ROM), and you have access to a Game Boy you can test on, please confirm SameBoy is indeed behaving differently from hardware, and report both the emulated model and revision in SameBoy, and the hardware revision you're testing on.
If your issue is a feature request, demonstrating use cases can help me better prioritize it.
## Pull Requests
To allow quicker integration into SameBoy's master branch, contributors are asked to follow SameBoy's style and coding guidelines. Keep in mind that despite the seemingly strict guidelines, all pull requests are welcome not following the guidelines does not mean your pull request will not be accepted, but it will require manual tweaks from my side for integrating.
### Languages and Compilers
SameBoy's core, SDL frontend, Libretro frontend, and automatic tester (Folders `Core`, `SDL` & `OpenDialog`, `libretro`, and `Tester`; respectively) are all written in C11. The Cocoa frontend, SameBoy's fork of Hex Fiend, JoyKit and the Quick Look previewer (Folders `Cocoa`, `HexFiend`, `JoyKit` and `QuickLook`; respectively) are all written in ARC-enabled Objective-C. The SameBoot ROMs (Under `BootROMs`) are written in rgbds-flavor SM83 assembly, with build tools in C11. The shaders (inside `Shaders`) are written in a polyglot GLSL and Metal style, with a few GLSL- and Metal-specific sources. The build system uses standalone Make, in the GNU flavor. Avoid adding new languages (C++, Swift, Python, CMake...) to any of the existing sub-projects.
SameBoy's main target compiler is Clang, but GCC is also supported when targeting Linux and Libretro. Other compilers (e.g. MSVC) are not supported, and unless there's a good reason, there's no need to go out of your way to add specific support for them. Extensions that are supported by both compilers (Such as `typeof`) may be used if it makes sense. It's OK if you can't test one of these compilers yourself; once you push a commit, the CI bot will let you know if you broke something.
### Third Party Libraries and Tools
Avoid adding new required dependencies; run-time and compile-time dependencies alike. Most importantly, avoid linking against GPL licensed libraries (LGPL libraries are fine), so SameBoy can retain its MIT license.
### Spacing, Indentation and Formatting
In all files and languages (Other than Makefiles when required), 4 spaces are used for indentation. Unix line endings (`\n`) are used exclusively, even in Windows-specific source files. (`\r` and `\t` shouldn't appear in any source file). Opening braces belong on the same line as their control flow directive, and on their own line when following a function prototype. The `else` keyword always starts on its own line. The `case` keyword is indented relative to its `switch` block, and the code inside a `case` is indented relative to its label. A control flow keyword should have a space between it and the following `(`, commas should follow a space, and operator (except `.` and `->`) should be surrounded by spaces.
Control flow statements must use `{}`, with the exception of `if` statements that only contain a single `break`, `continue`, or trivial `return` statements. If `{}`s are omitted, the statement must be on the same line as the `if` condition. Functions that do not have any argument must be specified as `(void)`, as mandated by the C standard. The `sizeof` and `typeof` operators should be used as if they're functions (With `()`). `*`, when used to declare pointer types (including functions that return pointers), and when used to dereference a pointer, is attached to the right side (The variable name) not to the left, and not with spaces on both sides.
No strict limitations on a line's maximum width, but use your best judgement if you think a statement would benefit from an additional line break.
Well formatted code example:
```
static void my_function(void)
{
GB_something_t *thing = GB_function(&gb, GB_FLAG_ONE | GB_FLAG_TWO, sizeof(thing));
if (GB_is_thing(thing)) return;
switch (*thing) {
case GB_QUACK:
// Something
case GB_DUCK:
// Something else
}
}
```
Badly formatted code example:
```
static void my_function(){
GB_something_t* thing=GB_function(&gb , GB_FLAG_ONE|GB_FLAG_TWO , sizeof thing);
if( GB_is_thing ( thing ) )
return;
switch(* thing)
{
case GB_QUACK:
// Something
case GB_DUCK:
// Something else
}
}
```
### Other Coding Conventions
The primitive types to be used in SameBoy are `unsigned` and `signed` (Without the `int` keyword), the `(u)int*_t` types, `char *` for UTF-8 strings, `double` for non-integer numbers, and `bool` for booleans (Including in Objective-C code, avoid `BOOL`). As long as it's not mandated by a 3rd-party API (e.g. `int` when using file descriptors), avoid using other primitive types. Use `const` whenever possible.
Most C names should be `lower_case_snake_case`. Constants and macros use `UPPER_CASE_SNAKE_CASE`. Type definitions use a `_t` suffix. Type definitions, as well as non-static (exported) core symbols, should be prefixed with `GB_` (SameBoy's core is intended to be used as a library, so it shouldn't contaminate the global namespace without prefixes). Exported symbols that are only meant to be used by other parts of the core should still get the `GB_` prefix, but their header definition should be inside `#ifdef GB_INTERNAL`.
For Objective-C naming conventions, use Apple's conventions (Some old Objective-C code mixes these with the C naming convention; new code should use Apple's convention exclusively). The name prefix for SameBoy classes and constants is `GB`. JoyKit's prefix is `JOY`, and Hex Fiend's prefix is `HF`.
In all languages, prefer long, unambiguous names over short ambiguous ones.

View File

@ -1,25 +0,0 @@
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate, WebUIDelegate, WebPolicyDelegate, WebFrameLoadDelegate>
@property (nonatomic, strong) IBOutlet NSWindow *preferencesWindow;
@property (nonatomic, strong) IBOutlet NSView *graphicsTab;
@property (nonatomic, strong) IBOutlet NSView *emulationTab;
@property (nonatomic, strong) IBOutlet NSView *audioTab;
@property (nonatomic, strong) IBOutlet NSView *controlsTab;
@property (nonatomic, strong) IBOutlet NSView *updatesTab;
- (IBAction)showPreferences: (id) sender;
- (IBAction)toggleDeveloperMode:(id)sender;
- (IBAction)switchPreferencesTab:(id)sender;
@property (nonatomic, weak) IBOutlet NSMenuItem *linkCableMenuItem;
@property (nonatomic, strong) IBOutlet NSWindow *updateWindow;
@property (nonatomic, strong) IBOutlet WebView *updateChanges;
@property (nonatomic, strong) IBOutlet NSProgressIndicator *updatesSpinner;
@property (strong) IBOutlet NSButton *updatesButton;
@property (strong) IBOutlet NSTextField *updateProgressLabel;
@property (strong) IBOutlet NSButton *updateProgressButton;
@property (strong) IBOutlet NSWindow *updateProgressWindow;
@property (strong) IBOutlet NSProgressIndicator *updateProgressSpinner;
@end

View File

@ -1,449 +0,0 @@
#import "AppDelegate.h"
#include "GBButtons.h"
#include "GBView.h"
#include <Core/gb.h>
#import <Carbon/Carbon.h>
#import <JoyKit/JoyKit.h>
#import <WebKit/WebKit.h>
#define UPDATE_SERVER "https://sameboy.github.io"
static uint32_t color_to_int(NSColor *color)
{
color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
return (((unsigned)(color.redComponent * 0xFF)) << 16) |
(((unsigned)(color.greenComponent * 0xFF)) << 8) |
((unsigned)(color.blueComponent * 0xFF));
}
@implementation AppDelegate
{
NSWindow *preferences_window;
NSArray<NSView *> *preferences_tabs;
NSString *_lastVersion;
NSString *_updateURL;
NSURLSessionDownloadTask *_updateTask;
enum {
UPDATE_DOWNLOADING,
UPDATE_EXTRACTING,
UPDATE_WAIT_INSTALL,
UPDATE_INSTALLING,
UPDATE_FAILED,
} _updateState;
NSString *_downloadDirectory;
}
- (void) applicationDidFinishLaunching:(NSNotification *)notification
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
for (unsigned i = 0; i < GBButtonCount; i++) {
if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) {
[defaults removeObjectForKey:button_to_preference_name(i, 0)];
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
@"GBRight": @(kVK_RightArrow),
@"GBLeft": @(kVK_LeftArrow),
@"GBUp": @(kVK_UpArrow),
@"GBDown": @(kVK_DownArrow),
@"GBA": @(kVK_ANSI_X),
@"GBB": @(kVK_ANSI_Z),
@"GBSelect": @(kVK_Delete),
@"GBStart": @(kVK_Return),
@"GBTurbo": @(kVK_Space),
@"GBRewind": @(kVK_Tab),
@"GBSlow-Motion": @(kVK_Shift),
@"GBFilter": @"NearestNeighbor",
@"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE),
@"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET),
@"GBRewindLength": @(10),
@"GBFrameBlendingMode": @([defaults boolForKey:@"DisableFrameBlending"]? GB_FRAME_BLENDING_MODE_DISABLED : GB_FRAME_BLENDING_MODE_ACCURATE),
@"GBDMGModel": @(GB_MODEL_DMG_B),
@"GBCGBModel": @(GB_MODEL_CGB_E),
@"GBSGBModel": @(GB_MODEL_SGB2),
@"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY),
@"GBVolume": @(1.0),
}];
[JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{
JOYAxes2DEmulateButtonsKey: @YES,
JOYHatsEmulateButtonsKey: @YES,
}];
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
}
[self askAutoUpdates];
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]) {
[self checkForUpdates];
}
if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) {
[NSApp activateIgnoringOtherApps:true];
}
}
- (IBAction)toggleDeveloperMode:(id)sender
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:![defaults boolForKey:@"DeveloperMode"] forKey:@"DeveloperMode"];
}
- (IBAction)switchPreferencesTab:(id)sender
{
for (NSView *view in preferences_tabs) {
[view removeFromSuperview];
}
NSView *tab = preferences_tabs[[sender tag]];
NSRect old = [_preferencesWindow frame];
NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame];
new.origin.x = old.origin.x;
new.origin.y = old.origin.y + (old.size.height - new.size.height);
[_preferencesWindow setFrame:new display:true animate:_preferencesWindow.visible];
[_preferencesWindow.contentView addSubview:tab];
}
- (BOOL)validateMenuItem:(NSMenuItem *)anItem
{
if ([anItem action] == @selector(toggleDeveloperMode:)) {
[(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]];
}
if (anItem == self.linkCableMenuItem) {
return [[NSDocumentController sharedDocumentController] documents].count > 1;
}
return true;
}
- (void)menuNeedsUpdate:(NSMenu *)menu
{
NSMutableArray *items = [NSMutableArray array];
NSDocument *currentDocument = [[NSDocumentController sharedDocumentController] currentDocument];
for (NSDocument *document in [[NSDocumentController sharedDocumentController] documents]) {
if (document == currentDocument) continue;
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:document.displayName action:@selector(connectLinkCable:) keyEquivalent:@""];
item.representedObject = document;
item.image = [[NSWorkspace sharedWorkspace] iconForFile:document.fileURL.path];
[item.image setSize:NSMakeSize(16, 16)];
[items addObject:item];
}
menu.itemArray = items;
}
- (IBAction) showPreferences: (id) sender
{
NSArray *objects;
if (!_preferencesWindow) {
[[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects];
NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject];
_preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier];
preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab, self.updatesTab];
[self switchPreferencesTab:first_toolbar_item];
[_preferencesWindow center];
#ifndef UPDATE_SUPPORT
[_preferencesWindow.toolbar removeItemAtIndex:4];
#endif
}
[_preferencesWindow makeKeyAndOrderFront:self];
}
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
{
[self askAutoUpdates];
/* Bring an existing panel to the foreground */
for (NSWindow *window in [[NSApplication sharedApplication] windows]) {
if ([window isKindOfClass:[NSOpenPanel class]]) {
[(NSOpenPanel *)window makeKeyAndOrderFront:nil];
return true;
}
}
[[NSDocumentController sharedDocumentController] openDocument:self];
return true;
}
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:true];
}
- (void)updateFound
{
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/raw_changes"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSColor *linkColor = [NSColor colorWithRed:0.125 green:0.325 blue:1.0 alpha:1.0];
if (@available(macOS 10.10, *)) {
linkColor = [NSColor linkColor];
}
NSString *changes = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSRange cutoffRange = [changes rangeOfString:@"<!--(" GB_VERSION ")-->"];
if (cutoffRange.location != NSNotFound) {
changes = [changes substringToIndex:cutoffRange.location];
}
NSString *html = [NSString stringWithFormat:@"<!DOCTYPE html><html><head><title></title>"
"<style>html {background-color:transparent; color: #%06x; line-height:1.5} a:link, a:visited{color:#%06x; text-decoration:none}</style>"
"</head><body>%@</body></html>",
color_to_int([NSColor textColor]),
color_to_int(linkColor),
changes];
if ([(NSHTTPURLResponse *)response statusCode] == 200) {
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *objects;
[[NSBundle mainBundle] loadNibNamed:@"UpdateWindow" owner:self topLevelObjects:&objects];
self.updateChanges.preferences.standardFontFamily = [NSFont systemFontOfSize:0].familyName;
self.updateChanges.preferences.fixedFontFamily = @"Menlo";
self.updateChanges.drawsBackground = false;
[self.updateChanges.mainFrame loadHTMLString:html baseURL:nil];
});
}
}] resume];
}
- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
{
// Disable reload context menu
if ([defaultMenuItems count] <= 2) {
return nil;
}
return defaultMenuItems;
}
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sender.mainFrame.frameView.documentView.enclosingScrollView.drawsBackground = true;
sender.mainFrame.frameView.documentView.enclosingScrollView.backgroundColor = [NSColor textBackgroundColor];
sender.policyDelegate = self;
[self.updateWindow center];
[self.updateWindow makeKeyAndOrderFront:nil];
});
}
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
{
[listener ignore];
[[NSWorkspace sharedWorkspace] openURL:[request URL]];
}
- (void)checkForUpdates
{
#ifdef UPDATE_SUPPORT
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/latest_version"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.updatesSpinner stopAnimation:nil];
[self.updatesButton setEnabled:true];
});
if ([(NSHTTPURLResponse *)response statusCode] == 200) {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray <NSString *> *components = [string componentsSeparatedByString:@"|"];
if (components.count != 2) return;
_lastVersion = components[0];
_updateURL = components[1];
if (![@GB_VERSION isEqualToString:_lastVersion] &&
![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBSkippedVersion"] isEqualToString:_lastVersion]) {
[self updateFound];
}
}
}] resume];
#endif
}
- (IBAction)userCheckForUpdates:(id)sender
{
if (self.updateWindow) {
[self.updateWindow makeKeyAndOrderFront:sender];
}
else {
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"GBSkippedVersion"];
[self checkForUpdates];
[sender setEnabled:false];
[self.updatesSpinner startAnimation:sender];
}
}
- (void)askAutoUpdates
{
#ifdef UPDATE_SUPPORT
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAskedAutoUpdates"]) {
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = @"Should SameBoy check for updates when launched?";
alert.informativeText = @"SameBoy is frequently updated with new features, accuracy improvements, and bug fixes. This setting can always be changed in the preferences window.";
[alert addButtonWithTitle:@"Check on Launch"];
[alert addButtonWithTitle:@"Don't Check on Launch"];
[[NSUserDefaults standardUserDefaults] setBool:[alert runModal] == NSAlertFirstButtonReturn forKey:@"GBAutoUpdatesEnabled"];
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBAskedAutoUpdates"];
}
#endif
}
- (IBAction)skipVersion:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:_lastVersion forKey:@"GBSkippedVersion"];
[self.updateWindow performClose:sender];
}
- (IBAction)installUpdate:(id)sender
{
[self.updateProgressSpinner startAnimation:nil];
self.updateProgressButton.title = @"Cancel";
self.updateProgressButton.enabled = true;
self.updateProgressLabel.stringValue = @"Downloading update...";
_updateState = UPDATE_DOWNLOADING;
_updateTask = [[NSURLSession sharedSession] downloadTaskWithURL: [NSURL URLWithString:_updateURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
_updateTask = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Extracting update...";
_updateState = UPDATE_EXTRACTING;
});
_downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask
appropriateForURL:[[NSBundle mainBundle] bundleURL]
create:true
error:nil] path];
NSTask *unzipTask;
if (!_downloadDirectory) {
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to extract update.";
_updateState = UPDATE_FAILED;
self.updateProgressButton.title = @"Close";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
}
unzipTask = [[NSTask alloc] init];
unzipTask.launchPath = @"/usr/bin/unzip";
unzipTask.arguments = @[location.path, @"-d", _downloadDirectory];
[unzipTask launch];
[unzipTask waitUntilExit];
if (unzipTask.terminationStatus != 0 || unzipTask.terminationReason != NSTaskTerminationReasonExit) {
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to extract update.";
_updateState = UPDATE_FAILED;
self.updateProgressButton.title = @"Close";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
return;
}
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Update ready, save your game progress and click Install.";
_updateState = UPDATE_WAIT_INSTALL;
self.updateProgressButton.title = @"Install";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
}];
[_updateTask resume];
self.updateProgressWindow.preventsApplicationTerminationWhenModal = false;
[self.updateWindow beginSheet:self.updateProgressWindow completionHandler:^(NSModalResponse returnCode) {
[self.updateWindow close];
}];
}
- (void)performUpgrade
{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Instaling update...";
_updateState = UPDATE_INSTALLING;
self.updateProgressButton.enabled = false;
[self.updateProgressSpinner startAnimation:nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *executablePath = [[NSBundle mainBundle] executablePath];
NSString *contentsPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents"];
NSString *contentsTempPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"TempContents"];
NSString *updateContentsPath = [_downloadDirectory stringByAppendingPathComponent:@"SameBoy.app/Contents"];
NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtPath:contentsPath toPath:contentsTempPath error:&error];
if (error) {
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
_downloadDirectory = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to install update.";
_updateState = UPDATE_FAILED;
self.updateProgressButton.title = @"Close";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
return;
}
[[NSFileManager defaultManager] moveItemAtPath:updateContentsPath toPath:contentsPath error:&error];
if (error) {
[[NSFileManager defaultManager] moveItemAtPath:contentsTempPath toPath:contentsPath error:nil];
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
_downloadDirectory = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to install update.";
_updateState = UPDATE_FAILED;
self.updateProgressButton.title = @"Close";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
return;
}
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
[[NSFileManager defaultManager] removeItemAtPath:contentsTempPath error:nil];
_downloadDirectory = nil;
atexit_b(^{
execl(executablePath.UTF8String, executablePath.UTF8String, "--update-launch", NULL);
});
dispatch_async(dispatch_get_main_queue(), ^{
[NSApp terminate:nil];
});
});
}
- (IBAction)updateAction:(id)sender
{
switch (_updateState) {
case UPDATE_DOWNLOADING:
[_updateTask cancelByProducingResumeData:nil];
_updateTask = nil;
[self.updateProgressWindow close];
break;
case UPDATE_WAIT_INSTALL:
[self performUpgrade];
break;
case UPDATE_EXTRACTING:
case UPDATE_INSTALLING:
break;
case UPDATE_FAILED:
[self.updateProgressWindow close];
break;
}
}
- (void)dealloc
{
if (_downloadDirectory) {
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
}
}
- (IBAction)nop:(id)sender
{
}
@end

Binary file not shown.

View File

@ -1,30 +0,0 @@
#import <Cocoa/Cocoa.h>
#ifndef BigSurToolbar_h
#define BigSurToolbar_h
/* Backport the toolbarStyle property to allow compilation with older SDKs*/
#ifndef __MAC_10_16
typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) {
// The default value. The style will be determined by the window's given configuration
NSWindowToolbarStyleAutomatic,
// The toolbar will appear below the window title
NSWindowToolbarStyleExpanded,
// The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible
NSWindowToolbarStylePreference,
// The window title will appear inline with the toolbar when visible
NSWindowToolbarStyleUnified,
// Same as NSWindowToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window
NSWindowToolbarStyleUnifiedCompact
} API_AVAILABLE(macos(11.0));
@interface NSWindow (toolbarStyle)
@property (nonatomic) NSWindowToolbarStyle toolbarStyle API_AVAILABLE(macos(11.0));
@end
@interface NSImage (SFSymbols)
+ (instancetype)imageWithSystemSymbolName:(NSString *)symbolName accessibilityDescription:(NSString *)description API_AVAILABLE(macos(11.0));
@end
#endif
#endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,61 +0,0 @@
#import <Cocoa/Cocoa.h>
#include "GBView.h"
#include "GBImageView.h"
#include "GBSplitView.h"
#include "GBVisualizerView.h"
#include "GBOSDView.h"
@class GBCheatWindowController;
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
@property (nonatomic, readonly) GB_gameboy_t *gb;
@property (nonatomic, strong) IBOutlet GBView *view;
@property (nonatomic, strong) IBOutlet NSTextView *consoleOutput;
@property (nonatomic, strong) IBOutlet NSPanel *consoleWindow;
@property (nonatomic, strong) IBOutlet NSTextField *consoleInput;
@property (nonatomic, strong) IBOutlet NSWindow *mainWindow;
@property (nonatomic, strong) IBOutlet NSView *memoryView;
@property (nonatomic, strong) IBOutlet NSPanel *memoryWindow;
@property (nonatomic, readonly) GB_gameboy_t *gameboy;
@property (nonatomic, strong) IBOutlet NSTextField *memoryBankInput;
@property (nonatomic, strong) IBOutlet NSToolbarItem *memoryBankItem;
@property (nonatomic, strong) IBOutlet GBImageView *tilesetImageView;
@property (nonatomic, strong) IBOutlet NSPopUpButton *tilesetPaletteButton;
@property (nonatomic, strong) IBOutlet GBImageView *tilemapImageView;
@property (nonatomic, strong) IBOutlet NSPopUpButton *tilemapPaletteButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *tilemapMapButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *TilemapSetButton;
@property (nonatomic, strong) IBOutlet NSButton *gridButton;
@property (nonatomic, strong) IBOutlet NSTabView *vramTabView;
@property (nonatomic, strong) IBOutlet NSPanel *vramWindow;
@property (nonatomic, strong) IBOutlet NSTextField *vramStatusLabel;
@property (nonatomic, strong) IBOutlet NSTableView *paletteTableView;
@property (nonatomic, strong) IBOutlet NSTableView *spritesTableView;
@property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow;
@property (nonatomic, strong) IBOutlet NSImageView *feedImageView;
@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput;
@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideView;
@property (nonatomic, strong) IBOutlet GBSplitView *debuggerSplitView;
@property (nonatomic, strong) IBOutlet NSBox *debuggerVerticalLine;
@property (nonatomic, strong) IBOutlet NSPanel *cheatsWindow;
@property (nonatomic, strong) IBOutlet GBCheatWindowController *cheatWindowController;
@property (nonatomic, readonly) Document *partner;
@property (nonatomic, readonly) bool isSlave;
@property (strong) IBOutlet NSView *gbsPlayerView;
@property (strong) IBOutlet NSTextField *gbsTitle;
@property (strong) IBOutlet NSTextField *gbsAuthor;
@property (strong) IBOutlet NSTextField *gbsCopyright;
@property (strong) IBOutlet NSPopUpButton *gbsTracks;
@property (strong) IBOutlet NSButton *gbsPlayPauseButton;
@property (strong) IBOutlet NSButton *gbsRewindButton;
@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton;
@property (strong) IBOutlet GBVisualizerView *gbsVisualizer;
@property (strong) IBOutlet GBOSDView *osdView;
-(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
-(void) performAtomicBlock: (void (^)())block;
-(void) connectLinkCable:(NSMenuItem *)sender;
-(int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound;
@end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +0,0 @@
#import <Foundation/Foundation.h>
#import <Core/gb.h>
@interface GBAudioClient : NSObject
@property (nonatomic, strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer);
@property (nonatomic, readonly) UInt32 rate;
@property (nonatomic, readonly, getter=isPlaying) bool playing;
-(void) start;
-(void) stop;
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
andSampleRate:(UInt32) rate;
@end

View File

@ -1,111 +0,0 @@
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import "GBAudioClient.h"
static OSStatus render(
GBAudioClient *self,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
GB_sample_t *buffer = (GB_sample_t *)ioData->mBuffers[0].mData;
self.renderBlock(self.rate, inNumberFrames, buffer);
return noErr;
}
@implementation GBAudioClient
{
AudioComponentInstance audioUnit;
}
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
andSampleRate:(UInt32) rate
{
if (!(self = [super init])) {
return nil;
}
// Configure the search parameters to find the default playback output unit
// (called the kAudioUnitSubType_RemoteIO on iOS but
// kAudioUnitSubType_DefaultOutput on Mac OS X)
AudioComponentDescription defaultOutputDescription;
defaultOutputDescription.componentType = kAudioUnitType_Output;
defaultOutputDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
defaultOutputDescription.componentFlags = 0;
defaultOutputDescription.componentFlagsMask = 0;
// Get the default playback output unit
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
NSAssert(defaultOutput, @"Can't find default output");
// Create a new unit based on this that we'll use for output
OSErr err = AudioComponentInstanceNew(defaultOutput, &audioUnit);
NSAssert1(audioUnit, @"Error creating unit: %hd", err);
// Set our tone rendering function on the unit
AURenderCallbackStruct input;
input.inputProc = (void*)render;
input.inputProcRefCon = (__bridge void * _Nullable)(self);
err = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&input,
sizeof(input));
NSAssert1(err == noErr, @"Error setting callback: %hd", err);
AudioStreamBasicDescription streamFormat;
streamFormat.mSampleRate = rate;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags =
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
streamFormat.mBytesPerPacket = 4;
streamFormat.mFramesPerPacket = 1;
streamFormat.mBytesPerFrame = 4;
streamFormat.mChannelsPerFrame = 2;
streamFormat.mBitsPerChannel = 2 * 8;
err = AudioUnitSetProperty (audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&streamFormat,
sizeof(AudioStreamBasicDescription));
NSAssert1(err == noErr, @"Error setting stream format: %hd", err);
err = AudioUnitInitialize(audioUnit);
NSAssert1(err == noErr, @"Error initializing unit: %hd", err);
self.renderBlock = block;
_rate = rate;
return self;
}
-(void) start
{
OSErr err = AudioOutputUnitStart(audioUnit);
NSAssert1(err == noErr, @"Error starting unit: %hd", err);
_playing = true;
}
-(void) stop
{
AudioOutputUnitStop(audioUnit);
_playing = false;
}
-(void) dealloc
{
[self stop];
AudioUnitUninitialize(audioUnit);
AudioComponentInstanceDispose(audioUnit);
}
@end

View File

@ -1,5 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBBorderView : NSView
@end

View File

@ -1,26 +0,0 @@
#import "GBBorderView.h"
@implementation GBBorderView
- (void)awakeFromNib
{
self.wantsLayer = true;
}
- (BOOL)wantsUpdateLayer
{
return true;
}
- (void)updateLayer
{
/* Wonderful, wonderful windowserver(?) bug. Using 0,0,0 here would cause it to render garbage
on fullscreen windows on some High Sierra machines. Any other value, including the one used
here (which is rendered exactly the same due to rounding) works around this bug. */
self.layer.backgroundColor = [NSColor colorWithCalibratedRed:0
green:0
blue:1.0 / 1024.0
alpha:1.0].CGColor;
}
@end

View File

@ -1,35 +0,0 @@
#ifndef GBButtons_h
#define GBButtons_h
typedef enum : NSUInteger {
GBRight,
GBLeft,
GBUp,
GBDown,
GBA,
GBB,
GBSelect,
GBStart,
GBTurbo,
GBRewind,
GBUnderclock,
GBButtonCount,
GBGameBoyButtonCount = GBStart + 1,
} GBButton;
extern NSString const *GBButtonNames[GBButtonCount];
static inline NSString *n2s(uint64_t number)
{
return [NSString stringWithFormat:@"%llx", number];
}
static inline NSString *button_to_preference_name(GBButton button, unsigned player)
{
if (player) {
return [NSString stringWithFormat:@"GBPlayer%d%@", player + 1, GBButtonNames[button]];
}
return [NSString stringWithFormat:@"GB%@", GBButtonNames[button]];
}
#endif

View File

@ -1,4 +0,0 @@
#import <Foundation/Foundation.h>
#import "GBButtons.h"
NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion"};

View File

@ -1,5 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBCheatTextFieldCell : NSTextFieldCell
@property (nonatomic) bool usesAddressFormat;
@end

View File

@ -1,121 +0,0 @@
#import "GBCheatTextFieldCell.h"
@interface GBCheatTextView : NSTextView
@property bool usesAddressFormat;
@end
@implementation GBCheatTextView
- (bool)_insertText:(NSString *)string replacementRange:(NSRange)range
{
if (range.location == NSNotFound) {
range = self.selectedRange;
}
NSString *new = [self.string stringByReplacingCharactersInRange:range withString:string];
if (!self.usesAddressFormat) {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\$[0-9A-Fa-f]{1,2}|[0-9]{1,3})$" options:0 error:NULL];
if ([regex numberOfMatchesInString:new options:0 range:NSMakeRange(0, new.length)]) {
[super insertText:string replacementRange:range];
return true;
}
if ([regex numberOfMatchesInString:[@"$" stringByAppendingString:new] options:0 range:NSMakeRange(0, new.length + 1)]) {
[super insertText:string replacementRange:range];
[super insertText:@"$" replacementRange:NSMakeRange(0, 0)];
return true;
}
if ([new isEqualToString:@"$"] || [string length] == 0) {
self.string = @"$00";
self.selectedRange = NSMakeRange(1, 2);
return true;
}
}
else {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\$[0-9A-Fa-f]{1,3}:)?\\$[0-9a-fA-F]{1,4}$" options:0 error:NULL];
if ([regex numberOfMatchesInString:new options:0 range:NSMakeRange(0, new.length)]) {
[super insertText:string replacementRange:range];
return true;
}
if ([string length] == 0) {
NSUInteger index = [new rangeOfString:@":"].location;
if (index != NSNotFound) {
if (range.location > index) {
self.string = [[new componentsSeparatedByString:@":"] firstObject];
self.selectedRange = NSMakeRange(self.string.length, 0);
return true;
}
self.string = [[new componentsSeparatedByString:@":"] lastObject];
self.selectedRange = NSMakeRange(0, 0);
return true;
}
else if ([[self.string substringWithRange:range] isEqualToString:@":"]) {
self.string = [[self.string componentsSeparatedByString:@":"] lastObject];
self.selectedRange = NSMakeRange(0, 0);
return true;
}
}
if ([new isEqualToString:@"$"] || [string length] == 0) {
self.string = @"$0000";
self.selectedRange = NSMakeRange(1, 4);
return true;
}
if (([string isEqualToString:@"$"] || [string isEqualToString:@":"]) && range.length == 0 && range.location == 0) {
if ([self _insertText:@"$00:" replacementRange:range]) {
self.selectedRange = NSMakeRange(1, 2);
return true;
}
}
if ([string isEqualToString:@":"] && range.length + range.location == self.string.length) {
if ([self _insertText:@":$0" replacementRange:range]) {
self.selectedRange = NSMakeRange(self.string.length - 2, 2);
return true;
}
}
if ([string isEqualToString:@"$"]) {
if ([self _insertText:@"$0" replacementRange:range]) {
self.selectedRange = NSMakeRange(range.location + 1, 1);
return true;
}
}
}
return false;
}
- (NSUndoManager *)undoManager
{
return nil;
}
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
if (![self _insertText:string replacementRange:replacementRange]) {
NSBeep();
}
}
/* Private API, don't tell the police! */
- (void)_userReplaceRange:(NSRange)range withString:(NSString *)string
{
[self insertText:string replacementRange:range];
}
@end
@implementation GBCheatTextFieldCell
{
bool _drawing, _editing;
GBCheatTextView *_fieldEditor;
}
- (NSTextView *)fieldEditorForView:(NSView *)controlView
{
if (_fieldEditor) {
_fieldEditor.usesAddressFormat = self.usesAddressFormat;
return _fieldEditor;
}
_fieldEditor = [[GBCheatTextView alloc] initWithFrame:controlView.frame];
_fieldEditor.fieldEditor = true;
_fieldEditor.usesAddressFormat = self.usesAddressFormat;
return _fieldEditor;
}
@end

View File

@ -1,16 +0,0 @@
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "Document.h"
@interface GBCheatWindowController : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate>
@property (nonatomic, weak) IBOutlet NSTableView *cheatsTable;
@property (nonatomic, weak) IBOutlet NSTextField *addressField;
@property (nonatomic, weak) IBOutlet NSTextField *valueField;
@property (nonatomic, weak) IBOutlet NSTextField *oldValueField;
@property (nonatomic, weak) IBOutlet NSButton *oldValueCheckbox;
@property (nonatomic, weak) IBOutlet NSTextField *descriptionField;
@property (nonatomic, weak) IBOutlet NSTextField *importCodeField;
@property (nonatomic, weak) IBOutlet NSTextField *importDescriptionField;
@property (nonatomic, weak) IBOutlet Document *document;
- (void)cheatsUpdated;
@end

View File

@ -1,240 +0,0 @@
#import "GBCheatWindowController.h"
#import "GBWarningPopover.h"
#import "GBCheatTextFieldCell.h"
@implementation GBCheatWindowController
+ (NSString *)addressStringFromCheat:(const GB_cheat_t *)cheat
{
if (cheat->bank != GB_CHEAT_ANY_BANK) {
return [NSString stringWithFormat:@"$%x:$%04x", cheat->bank, cheat->address];
}
return [NSString stringWithFormat:@"$%04x", cheat->address];
}
+ (NSString *)actionDescriptionForCheat:(const GB_cheat_t *)cheat
{
if (cheat->use_old_value) {
return [NSString stringWithFormat:@"[%@]($%02x) = $%02x", [self addressStringFromCheat:cheat], cheat->old_value, cheat->value];
}
return [NSString stringWithFormat:@"[%@] = $%02x", [self addressStringFromCheat:cheat], cheat->value];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return 0;
size_t cheatCount;
GB_get_cheats(gb, &cheatCount);
return cheatCount + 1;
}
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return nil;
size_t cheatCount;
GB_get_cheats(gb, &cheatCount);
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
if (row >= cheatCount && columnIndex == 0) {
return [[NSCell alloc] init];
}
return nil;
}
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
size_t cheatCount;
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return nil;
const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount);
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
if (row >= cheatCount) {
switch (columnIndex) {
case 0:
return @YES;
case 1:
return @NO;
case 2:
return @"Add Cheat...";
case 3:
return @"";
}
}
switch (columnIndex) {
case 0:
return @NO;
case 1:
return @(cheats[row]->enabled);
case 2:
return @(cheats[row]->description);
case 3:
return [GBCheatWindowController actionDescriptionForCheat:cheats[row]];
}
return nil;
}
- (IBAction)importCheat:(id)sender
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return;
[self.document performAtomicBlock:^{
if (GB_import_cheat(gb,
self.importCodeField.stringValue.UTF8String,
self.importDescriptionField.stringValue.UTF8String,
true)) {
self.importCodeField.stringValue = @"";
self.importDescriptionField.stringValue = @"";
[self.cheatsTable reloadData];
[self tableViewSelectionDidChange:nil];
}
else {
NSBeep();
[GBWarningPopover popoverWithContents:@"This code is not a valid GameShark or GameGenie code" onView:self.importCodeField];
}
}];
}
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return;
size_t cheatCount;
const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount);
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
[self.document performAtomicBlock:^{
if (columnIndex == 1) {
if (row >= cheatCount) {
GB_add_cheat(gb, "New Cheat", 0, 0, 0, 0, false, true);
}
else {
GB_update_cheat(gb, cheats[row], cheats[row]->description, cheats[row]->address, cheats[row]->bank, cheats[row]->value, cheats[row]->old_value, cheats[row]->use_old_value, !cheats[row]->enabled);
}
}
else if (row < cheatCount) {
GB_remove_cheat(gb, cheats[row]);
}
}];
[self.cheatsTable reloadData];
[self tableViewSelectionDidChange:nil];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return;
size_t cheatCount;
const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount);
unsigned row = self.cheatsTable.selectedRow;
const GB_cheat_t *cheat = NULL;
if (row >= cheatCount) {
static const GB_cheat_t template = {
.address = 0,
.bank = 0,
.value = 0,
.old_value = 0,
.use_old_value = false,
.enabled = false,
.description = "New Cheat",
};
cheat = &template;
}
else {
cheat = cheats[row];
}
self.addressField.stringValue = [GBCheatWindowController addressStringFromCheat:cheat];
self.valueField.stringValue = [NSString stringWithFormat:@"$%02x", cheat->value];
self.oldValueField.stringValue = [NSString stringWithFormat:@"$%02x", cheat->old_value];
self.oldValueCheckbox.state = cheat->use_old_value;
self.descriptionField.stringValue = @(cheat->description);
}
- (void)awakeFromNib
{
[self tableViewSelectionDidChange:nil];
((GBCheatTextFieldCell *)self.addressField.cell).usesAddressFormat = true;
}
- (void)controlTextDidChange:(NSNotification *)obj
{
[self updateCheat:nil];
}
- (IBAction)updateCheat:(id)sender
{
GB_gameboy_t *gb = self.document.gameboy;
if (!gb) return;
uint16_t address = 0;
uint16_t bank = GB_CHEAT_ANY_BANK;
if ([self.addressField.stringValue rangeOfString:@":"].location != NSNotFound) {
sscanf(self.addressField.stringValue.UTF8String, "$%hx:$%hx", &bank, &address);
}
else {
sscanf(self.addressField.stringValue.UTF8String, "$%hx", &address);
}
uint8_t value = 0;
if ([self.valueField.stringValue characterAtIndex:0] == '$') {
sscanf(self.valueField.stringValue.UTF8String, "$%02hhx", &value);
}
else {
sscanf(self.valueField.stringValue.UTF8String, "%hhd", &value);
}
uint8_t oldValue = 0;
if ([self.oldValueField.stringValue characterAtIndex:0] == '$') {
sscanf(self.oldValueField.stringValue.UTF8String, "$%02hhx", &oldValue);
}
else {
sscanf(self.oldValueField.stringValue.UTF8String, "%hhd", &oldValue);
}
size_t cheatCount;
const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount);
unsigned row = self.cheatsTable.selectedRow;
[self.document performAtomicBlock:^{
if (row >= cheatCount) {
GB_add_cheat(gb,
self.descriptionField.stringValue.UTF8String,
address,
bank,
value,
oldValue,
self.oldValueCheckbox.state,
false);
}
else {
GB_update_cheat(gb,
cheats[row],
self.descriptionField.stringValue.UTF8String,
address,
bank,
value,
oldValue,
self.oldValueCheckbox.state,
cheats[row]->enabled);
}
}];
[self.cheatsTable reloadData];
}
- (void)cheatsUpdated
{
[self.cheatsTable reloadData];
[self tableViewSelectionDidChange:nil];
}
@end

View File

@ -1,5 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBColorCell : NSTextFieldCell
@end

View File

@ -1,49 +0,0 @@
#import "GBColorCell.h"
static inline double scale_channel(uint8_t x)
{
x &= 0x1f;
return x / 31.0;
}
@implementation GBColorCell
{
NSInteger _integerValue;
}
- (void)setObjectValue:(id)objectValue
{
_integerValue = [objectValue integerValue];
uint8_t r = _integerValue & 0x1F,
g = (_integerValue >> 5) & 0x1F,
b = (_integerValue >> 10) & 0x1F;
super.objectValue = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)] attributes:@{
NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor],
NSFontAttributeName: [NSFont userFixedPitchFontOfSize:12]
}];
}
- (NSInteger)integerValue
{
return _integerValue;
}
- (int)intValue
{
return (int)_integerValue;
}
- (NSColor *) backgroundColor
{
/* Todo: color correction */
uint16_t color = self.integerValue;
return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0];
}
- (BOOL)drawsBackground
{
return true;
}
@end

View File

@ -1,7 +0,0 @@
#import "Document.h"
#import "HexFiend/HexFiend.h"
#import "HexFiend/HFByteSlice.h"
@interface GBCompleteByteSlice : HFByteSlice
- (instancetype) initWithByteArray:(HFByteArray *)array;
@end

View File

@ -1,26 +0,0 @@
#import "GBCompleteByteSlice.h"
@implementation GBCompleteByteSlice
{
HFByteArray *_array;
}
- (instancetype) initWithByteArray:(HFByteArray *)array
{
if ((self = [super init])) {
_array = array;
}
return self;
}
- (unsigned long long)length
{
return [_array length];
}
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range
{
[_array copyBytes:dst range:range];
}
@end

View File

@ -1,7 +0,0 @@
#import <Foundation/Foundation.h>
#import "GBView.h"
@interface GBGLShader : NSObject
- (instancetype)initWithName:(NSString *) shaderName;
- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode: (GB_frame_blending_mode_t)blendingMode;
@end

View File

@ -1,190 +0,0 @@
#import "GBGLShader.h"
#import <OpenGL/gl3.h>
/*
Loosely based of https://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial
This code probably makes no sense after I upgraded it to OpenGL 3, since OpenGL makes aboslute no sense and has zero
helpful documentation.
*/
static NSString * const vertex_shader = @"\n\
#version 150 \n\
in vec4 aPosition;\n\
void main(void) {\n\
gl_Position = aPosition;\n\
}\n\
";
@implementation GBGLShader
{
GLuint resolution_uniform;
GLuint texture_uniform;
GLuint previous_texture_uniform;
GLuint frame_blending_mode_uniform;
GLuint position_attribute;
GLuint texture;
GLuint previous_texture;
GLuint program;
}
+ (NSString *) shaderSourceForName:(NSString *) name
{
return [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name
ofType:@"fsh"
inDirectory:@"Shaders"]
encoding:NSUTF8StringEncoding
error:nil];
}
- (instancetype)initWithName:(NSString *) shaderName
{
self = [super init];
if (self) {
// Program
NSString *fragment_shader = [[self class] shaderSourceForName:@"MasterShader"];
fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"{filter}"
withString:[[self class] shaderSourceForName:shaderName]];
program = [[self class] programWithVertexShader:vertex_shader fragmentShader:fragment_shader];
// Attributes
position_attribute = glGetAttribLocation(program, "aPosition");
// Uniforms
resolution_uniform = glGetUniformLocation(program, "output_resolution");
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
texture_uniform = glGetUniformLocation(program, "image");
glGenTextures(1, &previous_texture);
glBindTexture(GL_TEXTURE_2D, previous_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
previous_texture_uniform = glGetUniformLocation(program, "previous_image");
frame_blending_mode_uniform = glGetUniformLocation(program, "frame_blending_mode");
// Configure OpenGL
[self configureOpenGL];
}
return self;
}
- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode:(GB_frame_blending_mode_t)blendingMode
{
glUseProgram(program);
glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap);
glUniform1i(texture_uniform, 0);
glUniform1i(frame_blending_mode_uniform, blendingMode);
if (blendingMode) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, previous_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous);
glUniform1i(previous_texture_uniform, 1);
}
glBindFragDataLocation(program, 0, "frag_color");
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
- (void)configureOpenGL
{
// Program
glUseProgram(program);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint vbo;
glGenBuffers(1, &vbo);
// Attributes
static GLfloat const quad[16] = {
-1.f, -1.f, 0, 1,
-1.f, +1.f, 0, 1,
+1.f, -1.f, 0, 1,
+1.f, +1.f, 0, 1,
};
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
glEnableVertexAttribArray(position_attribute);
glVertexAttribPointer(position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0);
}
+ (GLuint)programWithVertexShader:(NSString*)vsh fragmentShader:(NSString*)fsh
{
// Build shaders
GLuint vertex_shader = [self shaderWithContents:vsh type:GL_VERTEX_SHADER];
GLuint fragment_shader = [self shaderWithContents:fsh type:GL_FRAGMENT_SHADER];
// Create program
GLuint program = glCreateProgram();
// Attach shaders
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
// Link program
glLinkProgram(program);
// Check for errors
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
GLchar messages[1024];
glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
NSLog(@"%@:- GLSL Program Error: %s", self, messages);
}
// Delete shaders
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return program;
}
- (void)dealloc
{
glDeleteProgram(program);
glDeleteTextures(1, &texture);
glDeleteTextures(1, &previous_texture);
/* OpenGL is black magic. Closing one view causes others to be completely black unless we reload their shaders */
/* We're probably not freeing thing in the right place. */
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
}
+ (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type
{
const GLchar *source = [contents UTF8String];
// Create the shader object
GLuint shader = glCreateShader(type);
// Load the shader source
glShaderSource(shader, 1, &source, 0);
// Compile the shader
glCompileShader(shader);
// Check for errors
GLint status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLchar messages[1024];
glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
NSLog(@"%@:- GLSL Shader Error: %s", self, messages);
}
return shader;
}
@end

View File

@ -1,5 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBImageCell : NSImageCell
@end

View File

@ -1,10 +0,0 @@
#import "GBImageCell.h"
@implementation GBImageCell
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
[super drawWithFrame:cellFrame inView:controlView];
}
@end

View File

@ -1,23 +0,0 @@
#import <Cocoa/Cocoa.h>
@protocol GBImageViewDelegate;
@interface GBImageViewGridConfiguration : NSObject
@property (nonatomic, strong) NSColor *color;
@property (nonatomic) NSUInteger size;
- (instancetype) initWithColor: (NSColor *) color size: (NSUInteger) size;
@end
@interface GBImageView : NSImageView
@property (nonatomic, strong) NSArray<GBImageViewGridConfiguration *> *horizontalGrids;
@property (nonatomic, strong) NSArray<GBImageViewGridConfiguration *> *verticalGrids;
@property (nonatomic) bool displayScrollRect;
@property NSRect scrollRect;
@property (nonatomic, weak) IBOutlet id<GBImageViewDelegate> delegate;
@end
@protocol GBImageViewDelegate <NSObject>
@optional
- (void) mouseDidLeaveImageView: (GBImageView *)view;
- (void) imageView: (GBImageView *)view mouseMovedToX:(NSUInteger) x Y:(NSUInteger) y;
@end

View File

@ -1,127 +0,0 @@
#import "GBImageView.h"
@implementation GBImageViewGridConfiguration
- (instancetype)initWithColor:(NSColor *)color size:(NSUInteger)size
{
self = [super init];
self.color = color;
self.size = size;
return self;
}
@end
@implementation GBImageView
{
NSTrackingArea *trackingArea;
}
- (void)drawRect:(NSRect)dirtyRect
{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
[super drawRect:dirtyRect];
CGFloat y_ratio = self.frame.size.height / self.image.size.height;
CGFloat x_ratio = self.frame.size.width / self.image.size.width;
for (GBImageViewGridConfiguration *conf in self.verticalGrids) {
[conf.color set];
for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) {
NSBezierPath *line = [NSBezierPath bezierPath];
[line moveToPoint:NSMakePoint(0, y - 0.5)];
[line lineToPoint:NSMakePoint(self.frame.size.width, y - 0.5)];
[line setLineWidth:1.0];
[line stroke];
}
}
for (GBImageViewGridConfiguration *conf in self.horizontalGrids) {
[conf.color set];
for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) {
NSBezierPath *line = [NSBezierPath bezierPath];
[line moveToPoint:NSMakePoint(x + 0.5, 0)];
[line lineToPoint:NSMakePoint(x + 0.5, self.frame.size.height)];
[line setLineWidth:1.0];
[line stroke];
}
}
if (self.displayScrollRect) {
NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite];
for (unsigned x = 0; x < 2; x++) {
for (unsigned y = 0; y < 2; y++) {
NSRect rect = self.scrollRect;
rect.origin.x *= x_ratio;
rect.origin.y *= y_ratio;
rect.size.width *= x_ratio;
rect.size.height *= y_ratio;
rect.origin.y = self.frame.size.height - rect.origin.y - rect.size.height;
rect.origin.x -= self.frame.size.width * x;
rect.origin.y += self.frame.size.height * y;
NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect];
[path appendBezierPath:subpath];
}
}
[path setWindingRule:NSEvenOddWindingRule];
[path setLineWidth:4.0];
[path setLineJoinStyle:NSRoundLineJoinStyle];
[[NSColor colorWithWhite:0.2 alpha:0.5] set];
[path fill];
[path addClip];
[[NSColor colorWithWhite:0.0 alpha:0.6] set];
[path stroke];
}
}
- (void)setHorizontalGrids:(NSArray *)horizontalGrids
{
self->_horizontalGrids = horizontalGrids;
[self setNeedsDisplay];
}
- (void)setVerticalGrids:(NSArray *)verticalGrids
{
self->_verticalGrids = verticalGrids;
[self setNeedsDisplay];
}
- (void)setDisplayScrollRect:(bool)displayScrollRect
{
self->_displayScrollRect = displayScrollRect;
[self setNeedsDisplay];
}
- (void)updateTrackingAreas
{
if (trackingArea != nil) {
[self removeTrackingArea:trackingArea];
}
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}
- (void)mouseExited:(NSEvent *)theEvent
{
if ([self.delegate respondsToSelector:@selector(mouseDidLeaveImageView:)]) {
[self.delegate mouseDidLeaveImageView:self];
}
}
- (void)mouseMoved:(NSEvent *)theEvent
{
if ([self.delegate respondsToSelector:@selector(imageView:mouseMovedToX:Y:)]) {
NSPoint location = [self convertPoint:theEvent.locationInWindow fromView:nil];
location.x /= self.bounds.size.width;
location.y /= self.bounds.size.height;
location.y = 1 - location.y;
location.x *= self.image.size.width;
location.y *= self.image.size.height;
[self.delegate imageView:self mouseMovedToX:(NSUInteger)location.x Y:(NSUInteger)location.y];
}
}
@end

View File

@ -1,17 +0,0 @@
#import "Document.h"
#import "HexFiend/HexFiend.h"
#import "HexFiend/HFByteArray.h"
typedef enum {
GBMemoryEntireSpace,
GBMemoryROM,
GBMemoryVRAM,
GBMemoryExternalRAM,
GBMemoryRAM
} GB_memory_mode_t;
@interface GBMemoryByteArray : HFByteArray
- (instancetype) initWithDocument:(Document *)document;
@property (nonatomic) uint16_t selectedBank;
@property (nonatomic) GB_memory_mode_t mode;
@end

View File

@ -1,177 +0,0 @@
#define GB_INTERNAL // Todo: Some memory accesses are being done using the struct directly
#import "GBMemoryByteArray.h"
#import "GBCompleteByteSlice.h"
@implementation GBMemoryByteArray
{
Document *_document;
}
- (instancetype) initWithDocument:(Document *)document
{
if ((self = [super init])) {
_document = document;
}
return self;
}
- (unsigned long long)length
{
switch (_mode) {
case GBMemoryEntireSpace:
return 0x10000;
case GBMemoryROM:
return 0x8000;
case GBMemoryVRAM:
return 0x2000;
case GBMemoryExternalRAM:
return 0x2000;
case GBMemoryRAM:
return 0x2000;
}
}
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range
{
__block uint16_t addr = (uint16_t) range.location;
__block unsigned long long length = range.length;
if (_mode == GBMemoryEntireSpace) {
while (length) {
*(dst++) = [_document readMemory:addr++];
length--;
}
}
else {
[_document performAtomicBlock:^{
unsigned char *_dst = dst;
uint16_t bank_backup = 0;
GB_gameboy_t *gb = _document.gameboy;
switch (_mode) {
case GBMemoryROM:
bank_backup = gb->mbc_rom_bank;
gb->mbc_rom_bank = self.selectedBank;
break;
case GBMemoryVRAM:
bank_backup = gb->cgb_vram_bank;
if (GB_is_cgb(gb)) {
gb->cgb_vram_bank = self.selectedBank;
}
addr += 0x8000;
break;
case GBMemoryExternalRAM:
bank_backup = gb->mbc_ram_bank;
gb->mbc_ram_bank = self.selectedBank;
addr += 0xA000;
break;
case GBMemoryRAM:
bank_backup = gb->cgb_ram_bank;
if (GB_is_cgb(gb)) {
gb->cgb_ram_bank = self.selectedBank;
}
addr += 0xC000;
break;
default:
assert(false);
}
while (length) {
*(_dst++) = [_document readMemory:addr++];
length--;
}
switch (_mode) {
case GBMemoryROM:
gb->mbc_rom_bank = bank_backup;
break;
case GBMemoryVRAM:
gb->cgb_vram_bank = bank_backup;
break;
case GBMemoryExternalRAM:
gb->mbc_ram_bank = bank_backup;
break;
case GBMemoryRAM:
gb->cgb_ram_bank = bank_backup;
break;
default:
assert(false);
}
}];
}
}
- (NSArray *)byteSlices
{
return @[[[GBCompleteByteSlice alloc] initWithByteArray:self]];
}
- (HFByteArray *)subarrayWithRange:(HFRange)range
{
unsigned char arr[range.length];
[self copyBytes:arr range:range];
HFByteArray *ret = [[HFBTreeByteArray alloc] init];
HFFullMemoryByteSlice *slice = [[HFFullMemoryByteSlice alloc] initWithData:[NSData dataWithBytes:arr length:range.length]];
[ret insertByteSlice:slice inRange:HFRangeMake(0, 0)];
return ret;
}
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange
{
if (slice.length != lrange.length) return; /* Insertion is not allowed, only overwriting. */
[_document performAtomicBlock:^{
uint16_t addr = (uint16_t) lrange.location;
uint16_t bank_backup = 0;
GB_gameboy_t *gb = _document.gameboy;
switch (_mode) {
case GBMemoryROM:
bank_backup = gb->mbc_rom_bank;
gb->mbc_rom_bank = self.selectedBank;
break;
case GBMemoryVRAM:
bank_backup = gb->cgb_vram_bank;
if (GB_is_cgb(gb)) {
gb->cgb_vram_bank = self.selectedBank;
}
addr += 0x8000;
break;
case GBMemoryExternalRAM:
bank_backup = gb->mbc_ram_bank;
gb->mbc_ram_bank = self.selectedBank;
addr += 0xA000;
break;
case GBMemoryRAM:
bank_backup = gb->cgb_ram_bank;
if (GB_is_cgb(gb)) {
gb->cgb_ram_bank = self.selectedBank;
}
addr += 0xC000;
break;
default:
break;
}
uint8_t values[lrange.length];
[slice copyBytes:values range:HFRangeMake(0, lrange.length)];
uint8_t *src = values;
unsigned long long length = lrange.length;
while (length) {
[_document writeMemory:addr++ value:*(src++)];
length--;
}
switch (_mode) {
case GBMemoryROM:
gb->mbc_rom_bank = bank_backup;
break;
case GBMemoryVRAM:
gb->cgb_vram_bank = bank_backup;
break;
case GBMemoryExternalRAM:
gb->mbc_ram_bank = bank_backup;
break;
case GBMemoryRAM:
gb->cgb_ram_bank = bank_backup;
break;
default:
break;
}
}];
}
@end

View File

@ -1,6 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBOSDView : NSView
@property bool usesSGBScale;
- (void)displayText:(NSString *)text;
@end

View File

@ -1,104 +0,0 @@
#import "GBOSDView.h"
@implementation GBOSDView
{
bool _usesSGBScale;
NSString *_text;
double _animation;
NSTimer *_timer;
}
- (void)setUsesSGBScale:(bool)usesSGBScale
{
_usesSGBScale = usesSGBScale;
[self setNeedsDisplay:true];
}
- (bool)usesSGBScale
{
return _usesSGBScale;
}
- (void)displayText:(NSString *)text
{
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]) return;
dispatch_async(dispatch_get_main_queue(), ^{
if (![_text isEqualToString:text]) {
[self setNeedsDisplay:true];
}
_text = text;
self.alphaValue = 1.0;
_animation = 2.5;
// Longer strings should appear longer
if ([_text rangeOfString:@"\n"].location != NSNotFound) {
_animation += 4;
}
[_timer invalidate];
self.hidden = false;
_timer = [NSTimer scheduledTimerWithTimeInterval:0.025 target:self selector:@selector(animate) userInfo:nil repeats:true];
});
}
- (void)animate
{
_animation -= 0.1;
if (_animation < 1.0) {
self.alphaValue = _animation;
};
if (_animation == 0) {
self.hidden = true;
[_timer invalidate];
_text = nil;
}
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
if (!_text.length) return;
double fontSize = 8;
NSSize size = self.frame.size;
if (_usesSGBScale) {
fontSize *= MIN(size.width / 256, size.height / 224);
}
else {
fontSize *= MIN(size.width / 160, size.height / 144);
}
NSFont *font = [NSFont boldSystemFontOfSize:fontSize];
/* The built in stroke attribute uses an inside stroke, which is typographically terrible.
We'll use a naïve manual stroke instead which looks better. */
NSDictionary *attributes = @{
NSFontAttributeName: font,
NSForegroundColorAttributeName: [NSColor blackColor],
};
NSAttributedString *text = [[NSAttributedString alloc] initWithString:_text attributes:attributes];
[text drawAtPoint:NSMakePoint(fontSize + 1, fontSize + 0)];
[text drawAtPoint:NSMakePoint(fontSize - 1, fontSize + 0)];
[text drawAtPoint:NSMakePoint(fontSize + 0, fontSize + 1)];
[text drawAtPoint:NSMakePoint(fontSize + 0, fontSize - 1)];
// The uses of sqrt(2)/2, which is more correct, results in severe ugly-looking rounding errors
if (self.window.screen.backingScaleFactor > 1) {
[text drawAtPoint:NSMakePoint(fontSize + 0.5, fontSize + 0.5)];
[text drawAtPoint:NSMakePoint(fontSize - 0.5, fontSize + 0.5)];
[text drawAtPoint:NSMakePoint(fontSize - 0.5, fontSize - 0.5)];
[text drawAtPoint:NSMakePoint(fontSize + 0.5, fontSize - 0.5)];
}
attributes = @{
NSFontAttributeName: font,
NSForegroundColorAttributeName: [NSColor whiteColor],
};
text = [[NSAttributedString alloc] initWithString:_text attributes:attributes];
[text drawAtPoint:NSMakePoint(fontSize, fontSize)];
}
@end

View File

@ -1,6 +0,0 @@
#import <Cocoa/Cocoa.h>
#import "GBGLShader.h"
@interface GBOpenGLView : NSOpenGLView
@property (nonatomic) GBGLShader *shader;
@end

View File

@ -1,39 +0,0 @@
#import "GBOpenGLView.h"
#import "GBView.h"
#include <OpenGL/gl.h>
@implementation GBOpenGLView
- (void)drawRect:(NSRect)dirtyRect
{
if (!self.shader) {
self.shader = [[GBGLShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]];
}
GBView *gbview = (GBView *)self.superview;
double scale = self.window.backingScaleFactor;
glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale);
if (gbview.gb) {
[self.shader renderBitmap:gbview.currentBuffer
previous:gbview.frameBlendingMode? gbview.previousBuffer : NULL
sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb))
inSize:self.bounds.size
scale:scale
withBlendingMode:gbview.frameBlendingMode];
}
glFlush();
}
- (instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat *)format
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil];
return [super initWithFrame:frameRect pixelFormat:format];
}
- (void) filterChanged
{
self.shader = nil;
[self setNeedsDisplay:true];
}
@end

View File

@ -1,6 +0,0 @@
#import <Cocoa/Cocoa.h>
/* Fake interface so the compiler assumes it conforms to NSVisualEffectView */
@interface GBOptionalVisualEffectView : NSVisualEffectView
@end

View File

@ -1,18 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBOptionalVisualEffectView : NSView
@end
@implementation GBOptionalVisualEffectView
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
Class NSVisualEffectView = NSClassFromString(@"NSVisualEffectView");
if (NSVisualEffectView) {
return (id)[NSVisualEffectView alloc];
}
return [super allocWithZone:zone];
}
@end

View File

@ -1,32 +0,0 @@
#import <Cocoa/Cocoa.h>
#import <JoyKit/JoyKit.h>
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, JOYListener>
@property (nonatomic, strong) IBOutlet NSTableView *controlsTableView;
@property (nonatomic, strong) IBOutlet NSPopUpButton *graphicsFilterPopupButton;
@property (nonatomic, strong) IBOutlet NSButton *analogControlsCheckbox;
@property (nonatomic, strong) IBOutlet NSButton *aspectRatioCheckbox;
@property (nonatomic, strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *colorPalettePopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *displayBorderPopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *rewindPopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *rtcPopupButton;
@property (nonatomic, strong) IBOutlet NSButton *configureJoypadButton;
@property (nonatomic, strong) IBOutlet NSButton *skipButton;
@property (nonatomic, strong) IBOutlet NSMenuItem *bootROMsFolderItem;
@property (nonatomic, strong) IBOutlet NSPopUpButtonCell *bootROMsButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *rumbleModePopupButton;
@property (nonatomic, weak) IBOutlet NSSlider *temperatureSlider;
@property (nonatomic, weak) IBOutlet NSSlider *interferenceSlider;
@property (nonatomic, weak) IBOutlet NSPopUpButton *dmgPopupButton;
@property (nonatomic, weak) IBOutlet NSPopUpButton *sgbPopupButton;
@property (nonatomic, weak) IBOutlet NSPopUpButton *cgbPopupButton;
@property (nonatomic, weak) IBOutlet NSPopUpButton *preferredJoypadButton;
@property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton;
@property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox;
@property (weak) IBOutlet NSSlider *volumeSlider;
@property (weak) IBOutlet NSButton *OSDCheckbox;
@property (weak) IBOutlet NSButton *screenshotFilterCheckbox;
@end

View File

@ -1,794 +0,0 @@
#import "GBPreferencesWindow.h"
#import "NSString+StringForKey.h"
#import "GBButtons.h"
#import "BigSurToolbar.h"
#import "GBViewMetal.h"
#import <Carbon/Carbon.h>
@implementation GBPreferencesWindow
{
bool is_button_being_modified;
NSInteger button_being_modified;
signed joystick_configuration_state;
NSString *joystick_being_configured;
bool joypad_wait;
NSPopUpButton *_graphicsFilterPopupButton;
NSPopUpButton *_highpassFilterPopupButton;
NSPopUpButton *_colorCorrectionPopupButton;
NSPopUpButton *_frameBlendingModePopupButton;
NSPopUpButton *_colorPalettePopupButton;
NSPopUpButton *_displayBorderPopupButton;
NSPopUpButton *_rewindPopupButton;
NSPopUpButton *_rtcPopupButton;
NSButton *_aspectRatioCheckbox;
NSButton *_analogControlsCheckbox;
NSEventModifierFlags previousModifiers;
NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton;
NSPopUpButton *_preferredJoypadButton;
NSPopUpButton *_rumbleModePopupButton;
NSSlider *_temperatureSlider;
NSSlider *_interferenceSlider;
NSSlider *_volumeSlider;
NSButton *_autoUpdatesCheckbox;
NSButton *_OSDCheckbox;
NSButton *_screenshotFilterCheckbox;
}
+ (NSArray *)filterList
{
/* The filter list as ordered in the popup button */
static NSArray * filters = nil;
if (!filters) {
filters = @[
@"NearestNeighbor",
@"Bilinear",
@"SmoothBilinear",
@"MonoLCD",
@"LCD",
@"CRT",
@"Scale2x",
@"Scale4x",
@"AAScale2x",
@"AAScale4x",
@"HQ2x",
@"OmniScale",
@"OmniScaleLegacy",
@"AAOmniScaleLegacy",
];
}
return filters;
}
- (NSWindowToolbarStyle)toolbarStyle
{
return NSWindowToolbarStylePreference;
}
- (void)close
{
joystick_configuration_state = -1;
[self.configureJoypadButton setEnabled:true];
[self.skipButton setEnabled:false];
[self.configureJoypadButton setTitle:@"Configure Controller"];
[super close];
}
- (NSPopUpButton *)graphicsFilterPopupButton
{
return _graphicsFilterPopupButton;
}
- (void)setGraphicsFilterPopupButton:(NSPopUpButton *)graphicsFilterPopupButton
{
_graphicsFilterPopupButton = graphicsFilterPopupButton;
NSString *filter = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
[_graphicsFilterPopupButton selectItemAtIndex:[[[self class] filterList] indexOfObject:filter]];
}
- (NSPopUpButton *)highpassFilterPopupButton
{
return _highpassFilterPopupButton;
}
- (void)setColorCorrectionPopupButton:(NSPopUpButton *)colorCorrectionPopupButton
{
_colorCorrectionPopupButton = colorCorrectionPopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"];
[_colorCorrectionPopupButton selectItemAtIndex:mode];
}
- (NSPopUpButton *)colorCorrectionPopupButton
{
return _colorCorrectionPopupButton;
}
- (void)setTemperatureSlider:(NSSlider *)temperatureSlider
{
_temperatureSlider = temperatureSlider;
[temperatureSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"] * 256];
}
- (NSSlider *)temperatureSlider
{
return _temperatureSlider;
}
- (void)setInterferenceSlider:(NSSlider *)interferenceSlider
{
_interferenceSlider = interferenceSlider;
[interferenceSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"] * 256];
}
- (NSSlider *)interferenceSlider
{
return _interferenceSlider;
}
- (void)setVolumeSlider:(NSSlider *)volumeSlider
{
_volumeSlider = volumeSlider;
[volumeSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"] * 256];
}
- (NSSlider *)volumeSlider
{
return _volumeSlider;
}
- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton
{
_frameBlendingModePopupButton = frameBlendingModePopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
[_frameBlendingModePopupButton selectItemAtIndex:mode];
}
- (NSPopUpButton *)frameBlendingModePopupButton
{
return _frameBlendingModePopupButton;
}
- (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton
{
_colorPalettePopupButton = colorPalettePopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"];
[_colorPalettePopupButton selectItemAtIndex:mode];
}
- (NSPopUpButton *)colorPalettePopupButton
{
return _colorPalettePopupButton;
}
- (void)setDisplayBorderPopupButton:(NSPopUpButton *)displayBorderPopupButton
{
_displayBorderPopupButton = displayBorderPopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"];
[_displayBorderPopupButton selectItemWithTag:mode];
}
- (NSPopUpButton *)displayBorderPopupButton
{
return _displayBorderPopupButton;
}
- (void)setRumbleModePopupButton:(NSPopUpButton *)rumbleModePopupButton
{
_rumbleModePopupButton = rumbleModePopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"];
[_rumbleModePopupButton selectItemWithTag:mode];
}
- (NSPopUpButton *)rumbleModePopupButton
{
return _rumbleModePopupButton;
}
- (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton
{
_rewindPopupButton = rewindPopupButton;
NSInteger length = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"];
[_rewindPopupButton selectItemWithTag:length];
}
- (NSPopUpButton *)rewindPopupButton
{
return _rewindPopupButton;
}
- (NSPopUpButton *)rtcPopupButton
{
return _rtcPopupButton;
}
- (void)setRtcPopupButton:(NSPopUpButton *)rtcPopupButton
{
_rtcPopupButton = rtcPopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"];
[_rtcPopupButton selectItemAtIndex:mode];
}
- (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton
{
_highpassFilterPopupButton = highpassFilterPopupButton;
[_highpassFilterPopupButton selectItemAtIndex:[[[NSUserDefaults standardUserDefaults] objectForKey:@"GBHighpassFilter"] unsignedIntegerValue]];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
if (self.playerListButton.selectedTag == 0) {
return GBButtonCount;
}
return GBGameBoyButtonCount;
}
- (unsigned) usesForKey:(unsigned) key
{
unsigned ret = 0;
for (unsigned player = 4; player--;) {
for (unsigned button = player == 0? GBButtonCount:GBGameBoyButtonCount; button--;) {
NSNumber *other = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(button, player)];
if (other && [other unsignedIntValue] == key) {
ret++;
}
}
}
return ret;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if ([tableColumn.identifier isEqualToString:@"keyName"]) {
return GBButtonNames[row];
}
if (is_button_being_modified && button_being_modified == row) {
return @"Select a new key...";
}
NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];
if (key) {
if ([self usesForKey:[key unsignedIntValue]] > 1) {
return [[NSAttributedString alloc] initWithString:[NSString displayStringForKeyCode: [key unsignedIntegerValue]]
attributes:@{NSForegroundColorAttributeName: [NSColor colorWithRed:0.9375 green:0.25 blue:0.25 alpha:1.0],
NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]
}];
}
return [NSString displayStringForKeyCode: [key unsignedIntegerValue]];
}
return @"";
}
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
dispatch_async(dispatch_get_main_queue(), ^{
is_button_being_modified = true;
button_being_modified = row;
tableView.enabled = false;
self.playerListButton.enabled = false;
[tableView reloadData];
[self makeFirstResponder:self];
});
return false;
}
-(void)keyDown:(NSEvent *)theEvent
{
if (!is_button_being_modified) {
if (self.firstResponder != self.controlsTableView && [theEvent type] != NSEventTypeFlagsChanged) {
[super keyDown:theEvent];
}
return;
}
is_button_being_modified = false;
[[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode
forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)];
self.controlsTableView.enabled = true;
self.playerListButton.enabled = true;
[self.controlsTableView reloadData];
[self makeFirstResponder:self.controlsTableView];
}
- (void) flagsChanged:(NSEvent *)event
{
if (event.modifierFlags > previousModifiers) {
[self keyDown:event];
}
previousModifiers = event.modifierFlags;
}
- (IBAction)graphicFilterChanged:(NSPopUpButton *)sender
{
[[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]]
forKey:@"GBFilter"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
}
- (IBAction)highpassFilterChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBHighpassFilter"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil];
}
- (IBAction)changeAnalogControls:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
forKey:@"GBAnalogControls"];
}
- (IBAction)changeAspectRatio:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState
forKey:@"GBAspectRatioUnkept"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBAspectChanged" object:nil];
}
- (IBAction)colorCorrectionChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBColorCorrection"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil];
}
- (IBAction)lightTemperatureChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
forKey:@"GBLightTemperature"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil];
}
- (IBAction)interferenceVolumeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
forKey:@"GBInterferenceVolume"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil];
}
- (IBAction)volumeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
forKey:@"GBVolume"];
}
- (IBAction)franeBlendingModeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBFrameBlendingMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFrameBlendingModeChanged" object:nil];
}
- (IBAction)colorPaletteChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBColorPalette"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
}
- (IBAction)displayBorderChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
forKey:@"GBBorderMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil];
}
- (IBAction)rumbleModeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
forKey:@"GBRumbleMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRumbleModeChanged" object:nil];
}
- (IBAction)rewindLengthChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
forKey:@"GBRewindLength"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil];
}
- (IBAction)rtcModeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBRTCMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRTCModeChanged" object:nil];
}
- (IBAction)changeAutoUpdates:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
forKey:@"GBAutoUpdatesEnabled"];
}
- (IBAction) configureJoypad:(id)sender
{
[self.configureJoypadButton setEnabled:false];
[self.skipButton setEnabled:true];
joystick_being_configured = nil;
[self advanceConfigurationStateMachine];
}
- (IBAction) skipButton:(id)sender
{
[self advanceConfigurationStateMachine];
}
- (void) advanceConfigurationStateMachine
{
joystick_configuration_state++;
if (joystick_configuration_state == GBUnderclock) {
[self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :<
}
else if (joystick_configuration_state < GBButtonCount) {
[self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
}
else {
joystick_configuration_state = -1;
[self.configureJoypadButton setEnabled:true];
[self.skipButton setEnabled:false];
[self.configureJoypadButton setTitle:@"Configure Joypad"];
}
}
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
{
/* Debounce */
if (joypad_wait) return;
joypad_wait = true;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
joypad_wait = false;
});
if (!button.isPressed) return;
if (joystick_configuration_state == -1) return;
if (joystick_configuration_state == GBButtonCount) return;
if (!joystick_being_configured) {
joystick_being_configured = controller.uniqueID;
}
else if (![joystick_being_configured isEqualToString:controller.uniqueID]) {
return;
}
NSMutableDictionary *instance_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"] mutableCopy];
NSMutableDictionary *name_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"] mutableCopy];
if (!instance_mappings) {
instance_mappings = [[NSMutableDictionary alloc] init];
}
if (!name_mappings) {
name_mappings = [[NSMutableDictionary alloc] init];
}
NSMutableDictionary *mapping = nil;
if (joystick_configuration_state != 0) {
mapping = [instance_mappings[controller.uniqueID] mutableCopy];
}
else {
mapping = [[NSMutableDictionary alloc] init];
}
static const unsigned gb_to_joykit[] = {
[GBRight] = JOYButtonUsageDPadRight,
[GBLeft] = JOYButtonUsageDPadLeft,
[GBUp] = JOYButtonUsageDPadUp,
[GBDown] = JOYButtonUsageDPadDown,
[GBA] = JOYButtonUsageA,
[GBB] = JOYButtonUsageB,
[GBSelect] = JOYButtonUsageSelect,
[GBStart] = JOYButtonUsageStart,
[GBTurbo] = JOYButtonUsageL1,
[GBRewind] = JOYButtonUsageL2,
[GBUnderclock] = JOYButtonUsageR1,
};
if (joystick_configuration_state == GBUnderclock) {
mapping[@"AnalogUnderclock"] = nil;
double max = 0;
for (JOYAxis *axis in controller.axes) {
if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) {
max = axis.value;
mapping[@"AnalogUnderclock"] = @(axis.uniqueID);
break;
}
}
}
if (joystick_configuration_state == GBTurbo) {
mapping[@"AnalogTurbo"] = nil;
double max = 0;
for (JOYAxis *axis in controller.axes) {
if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) {
max = axis.value;
mapping[@"AnalogTurbo"] = @(axis.uniqueID);
}
}
}
mapping[n2s(button.uniqueID)] = @(gb_to_joykit[joystick_configuration_state]);
instance_mappings[controller.uniqueID] = mapping;
name_mappings[controller.deviceName] = mapping;
[[NSUserDefaults standardUserDefaults] setObject:instance_mappings forKey:@"JoyKitInstanceMapping"];
[[NSUserDefaults standardUserDefaults] setObject:name_mappings forKey:@"JoyKitNameMapping"];
[self advanceConfigurationStateMachine];
}
- (NSButton *)analogControlsCheckbox
{
return _analogControlsCheckbox;
}
- (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox
{
_analogControlsCheckbox = analogControlsCheckbox;
[_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]];
}
- (NSButton *)aspectRatioCheckbox
{
return _aspectRatioCheckbox;
}
- (void)setAspectRatioCheckbox:(NSButton *)aspectRatioCheckbox
{
_aspectRatioCheckbox = aspectRatioCheckbox;
[_aspectRatioCheckbox setState: ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self updateBootROMFolderButton];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil];
[JOYController registerListener:self];
joystick_configuration_state = -1;
}
- (void)dealloc
{
[JOYController unregisterListener:self];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView];
}
- (IBAction)selectOtherBootROMFolder:(id)sender
{
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
[panel setCanChooseDirectories:true];
[panel setCanChooseFiles:false];
[panel setPrompt:@"Select"];
[panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]];
[panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) {
if (result == NSModalResponseOK) {
NSURL *url = [[panel URLs] firstObject];
[[NSUserDefaults standardUserDefaults] setURL:url forKey:@"GBBootROMsFolder"];
}
[self updateBootROMFolderButton];
}];
}
- (void) updateBootROMFolderButton
{
NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"];
BOOL is_dir = false;
[[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&is_dir];
if (!is_dir) url = nil;
if (url) {
[self.bootROMsFolderItem setTitle:[url lastPathComponent]];
NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]];
[icon setSize:NSMakeSize(16, 16)];
[self.bootROMsFolderItem setHidden:false];
[self.bootROMsFolderItem setImage:icon];
[self.bootROMsButton selectItemAtIndex:1];
}
else {
[self.bootROMsFolderItem setHidden:true];
[self.bootROMsButton selectItemAtIndex:0];
}
}
- (IBAction)useBuiltinBootROMs:(id)sender
{
[[NSUserDefaults standardUserDefaults] setURL:nil forKey:@"GBBootROMsFolder"];
[self updateBootROMFolderButton];
}
- (void)setDmgPopupButton:(NSPopUpButton *)dmgPopupButton
{
_dmgPopupButton = dmgPopupButton;
[_dmgPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"]];
}
- (NSPopUpButton *)dmgPopupButton
{
return _dmgPopupButton;
}
- (void)setSgbPopupButton:(NSPopUpButton *)sgbPopupButton
{
_sgbPopupButton = sgbPopupButton;
[_sgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]];
}
- (NSPopUpButton *)sgbPopupButton
{
return _sgbPopupButton;
}
- (void)setCgbPopupButton:(NSPopUpButton *)cgbPopupButton
{
_cgbPopupButton = cgbPopupButton;
[_cgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]];
}
- (NSPopUpButton *)cgbPopupButton
{
return _cgbPopupButton;
}
- (IBAction)dmgModelChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
forKey:@"GBDMGModel"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBDMGModelChanged" object:nil];
}
- (IBAction)sgbModelChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
forKey:@"GBSGBModel"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBSGBModelChanged" object:nil];
}
- (IBAction)cgbModelChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
forKey:@"GBCGBModel"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBCGBModelChanged" object:nil];
}
- (IBAction)reloadButtonsData:(id)sender
{
[self.controlsTableView reloadData];
}
- (void)setPreferredJoypadButton:(NSPopUpButton *)preferredJoypadButton
{
_preferredJoypadButton = preferredJoypadButton;
[self refreshJoypadMenu:nil];
}
- (NSPopUpButton *)preferredJoypadButton
{
return _preferredJoypadButton;
}
- (void)controllerConnected:(JOYController *)controller
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self refreshJoypadMenu:nil];
});
}
- (void)controllerDisconnected:(JOYController *)controller
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self refreshJoypadMenu:nil];
});
}
- (IBAction)refreshJoypadMenu:(id)sender
{
bool preferred_is_connected = false;
NSString *player_string = n2s(self.playerListButton.selectedTag);
NSString *selected_controller = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"][player_string];
[self.preferredJoypadButton removeAllItems];
[self.preferredJoypadButton addItemWithTitle:@"None"];
for (JOYController *controller in [JOYController allControllers]) {
[self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"%@ (%@)", controller.deviceName, controller.uniqueID]];
self.preferredJoypadButton.lastItem.identifier = controller.uniqueID;
if ([controller.uniqueID isEqualToString:selected_controller]) {
preferred_is_connected = true;
[self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
}
}
if (!preferred_is_connected && selected_controller) {
[self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"Unavailable Controller (%@)", selected_controller]];
self.preferredJoypadButton.lastItem.identifier = selected_controller;
[self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
}
if (!selected_controller) {
[self.preferredJoypadButton selectItemWithTitle:@"None"];
}
[self.controlsTableView reloadData];
}
- (IBAction)changeDefaultJoypad:(id)sender
{
NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] mutableCopy];
if (!default_joypads) {
default_joypads = [[NSMutableDictionary alloc] init];
}
NSString *player_string = n2s(self.playerListButton.selectedTag);
if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) {
[default_joypads removeObjectForKey:player_string];
}
else {
default_joypads[player_string] = [[sender selectedItem] identifier];
}
[[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"];
}
- (NSButton *)autoUpdatesCheckbox
{
return _autoUpdatesCheckbox;
}
- (void)setAutoUpdatesCheckbox:(NSButton *)autoUpdatesCheckbox
{
_autoUpdatesCheckbox = autoUpdatesCheckbox;
[_autoUpdatesCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]];
}
- (NSButton *)OSDCheckbox
{
return _OSDCheckbox;
}
- (void)setOSDCheckbox:(NSButton *)OSDCheckbox
{
_OSDCheckbox = OSDCheckbox;
[_OSDCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]];
}
- (IBAction)changeOSDEnabled:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState
forKey:@"GBOSDEnabled"];
}
- (IBAction)changeFilterScreenshots:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState
forKey:@"GBFilterScreenshots"];
}
- (NSButton *)screenshotFilterCheckbox
{
return _screenshotFilterCheckbox;
}
- (void)setScreenshotFilterCheckbox:(NSButton *)screenshotFilterCheckbox
{
_screenshotFilterCheckbox = screenshotFilterCheckbox;
if (![GBViewMetal isSupported]) {
[_screenshotFilterCheckbox setEnabled:false];
}
else {
[_screenshotFilterCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]];
}
}
@end

View File

@ -1,128 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="Document">
<connections>
<outlet property="gbsAuthor" destination="gaD-ZH-Beh" id="2i7-BD-bJ2"/>
<outlet property="gbsCopyright" destination="2dl-dH-E3J" id="LnT-Vb-pN6"/>
<outlet property="gbsNextPrevButton" destination="SRS-M5-VVL" id="YEN-01-wRX"/>
<outlet property="gbsPlayPauseButton" destination="qxJ-pH-d0y" id="qk8-8I-9u5"/>
<outlet property="gbsPlayerView" destination="c22-O7-iKe" id="A1w-e5-EQE"/>
<outlet property="gbsRewindButton" destination="0yD-Sp-Ilo" id="FgR-xd-JW5"/>
<outlet property="gbsTitle" destination="H3v-X3-48q" id="DCl-wL-oy8"/>
<outlet property="gbsTracks" destination="I1T-VS-Vse" id="Vk4-GP-RjB"/>
<outlet property="gbsVisualizer" destination="Q3o-bK-DIN" id="1YC-C5-Je6"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe">
<rect key="frame" x="0.0" y="0.0" width="332" height="221"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="H3v-X3-48q">
<rect key="frame" x="18" y="192" width="296" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Title" id="BwZ-Zj-sP6">
<font key="font" metaFont="systemBold" size="16"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="gaD-ZH-Beh">
<rect key="frame" x="18" y="166" width="296" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Author" id="IgT-r1-T38">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qxJ-pH-d0y">
<rect key="frame" x="61.5" y="127" width="39" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Play" imagePosition="only" alignment="center" alternateImage="Pause" state="on" borderStyle="border" focusRingType="none" inset="2" id="3ZK-br-UrS">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="togglePause:" target="-2" id="AUe-I7-nOK"/>
</connections>
</button>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
<rect key="frame" x="19.5" y="127" width="38" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Rewind" imagePosition="only" alignment="center" state="on" borderStyle="border" focusRingType="none" inset="2" id="ZIF-TP-Fqn">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeGBSTrack:" target="-2" id="jug-AS-bW7"/>
</connections>
</button>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
<rect key="frame" x="106" y="127" width="131" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="YJh-dI-A5D">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="Knp-Ok-Pb4"/>
</popUpButtonCell>
<connections>
<action selector="changeGBSTrack:" target="-2" id="HET-AT-CfQ"/>
</connections>
</popUpButton>
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SRS-M5-VVL">
<rect key="frame" x="240.5" y="127" width="72" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="cmq-I8-cFL">
<font key="font" metaFont="system"/>
<segments>
<segment toolTip="Previous Track" image="Previous" width="33"/>
<segment toolTip="Next Track" image="Next" width="32" tag="1"/>
</segments>
</segmentedCell>
<connections>
<action selector="gbsNextPrevPushed:" target="-2" id="roN-Iy-tDQ"/>
</connections>
</segmentedControl>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="b9A-cd-ias">
<rect key="frame" x="0.0" y="117" width="332" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box>
<customView appearanceType="darkAqua" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tRy-Gw-IaG" customClass="GBOptionalVisualEffectView">
<rect key="frame" x="0.0" y="24" width="332" height="95"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q3o-bK-DIN" customClass="GBVisualizerView">
<rect key="frame" x="0.0" y="0.0" width="332" height="95"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
</subviews>
</customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="2dl-dH-E3J">
<rect key="frame" x="18" y="5" width="296" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Copyright" id="nM9-oF-OV9">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="67" y="292.5"/>
</customView>
</objects>
<resources>
<image name="Next" width="16" height="10"/>
<image name="Pause" width="10" height="10"/>
<image name="Play" width="10" height="10"/>
<image name="Previous" width="16" height="10"/>
<image name="Rewind" width="10" height="10"/>
</resources>
</document>

View File

@ -1,7 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBSplitView : NSSplitView
-(void) setDividerColor:(NSColor *)color;
- (NSArray<NSView *> *)arrangedSubviews;
@end

View File

@ -1,33 +0,0 @@
#import "GBSplitView.h"
@implementation GBSplitView
{
NSColor *_dividerColor;
}
- (void)setDividerColor:(NSColor *)color
{
_dividerColor = color;
[self setNeedsDisplay:true];
}
- (NSColor *)dividerColor
{
if (_dividerColor) {
return _dividerColor;
}
return [super dividerColor];
}
/* Mavericks comaptibility */
- (NSArray<NSView *> *)arrangedSubviews
{
if (@available(macOS 10.11, *)) {
return [super arrangedSubviews];
}
else {
return [self subviews];
}
}
@end

View File

@ -1,6 +0,0 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
@interface GBTerminalTextFieldCell : NSTextFieldCell
@property (nonatomic) GB_gameboy_t *gb;
@end

View File

@ -1,231 +0,0 @@
#import <Carbon/Carbon.h>
#import "GBTerminalTextFieldCell.h"
@interface GBTerminalTextView : NSTextView
@property GB_gameboy_t *gb;
@end
@implementation GBTerminalTextFieldCell
{
GBTerminalTextView *field_editor;
}
- (NSTextView *)fieldEditorForView:(NSView *)controlView
{
if (field_editor) {
field_editor.gb = self.gb;
return field_editor;
}
field_editor = [[GBTerminalTextView alloc] init];
[field_editor setFieldEditor:true];
field_editor.gb = self.gb;
return field_editor;
}
@end
@implementation GBTerminalTextView
{
NSMutableOrderedSet *lines;
NSUInteger current_line;
bool reverse_search_mode;
NSRange auto_complete_range;
uintptr_t auto_complete_context;
}
- (instancetype)init
{
self = [super init];
if (!self) {
return NULL;
}
lines = [[NSMutableOrderedSet alloc] init];
return self;
}
- (void)moveUp:(id)sender
{
reverse_search_mode = false;
if (current_line != 0) {
current_line--;
[self setString:[lines objectAtIndex:current_line]];
}
else {
[self setSelectedRange:NSMakeRange(0, 0)];
NSBeep();
}
}
- (void)moveDown:(id)sender
{
reverse_search_mode = false;
if (current_line == [lines count]) {
[self setString:@""];
NSBeep();
return;
}
current_line++;
if (current_line == [lines count]) {
[self setString:@""];
}
else {
[self setString:[lines objectAtIndex:current_line]];
}
}
-(void)insertNewline:(id)sender
{
if ([self.string length]) {
NSString *string = [self.string copy];
[lines removeObject:string];
[lines addObject:string];
}
[super insertNewline:sender];
current_line = [lines count];
reverse_search_mode = false;
}
- (void)keyDown:(NSEvent *)event
{
if (event.keyCode == kVK_ANSI_R && (event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagControl) {
if ([lines count] == 0) {
NSBeep();
return;
}
if (!reverse_search_mode) {
[self selectAll:self];
current_line = [lines count] - 1;
}
else {
if (current_line != 0) {
current_line--;
}
else {
NSBeep();
}
}
if (self.string.length) {
[self updateReverseSearch];
}
else {
[self setNeedsDisplay:true];
reverse_search_mode = true;
}
}
else {
[super keyDown:event];
}
}
- (void) updateReverseSearch
{
NSUInteger old_line = current_line;
reverse_search_mode = false;
NSString *substring = [self.string substringWithRange:self.selectedRange];
do {
NSString *line = [lines objectAtIndex:current_line];
NSRange range = [line rangeOfString:substring];
if (range.location != NSNotFound) {
self.string = line;
[self setSelectedRange:range];
reverse_search_mode = true;
return;
}
} while (current_line--);
current_line = old_line;
reverse_search_mode = true;
NSBeep();
}
- (void) insertText:(NSString *)string replacementRange:(NSRange)range
{
if (reverse_search_mode) {
range = self.selectedRange;
self.string = [[self.string substringWithRange:range] stringByAppendingString:string];
[self selectAll:nil];
[self updateReverseSearch];
}
else {
[super insertText:string replacementRange:range];
}
}
-(void)deleteBackward:(id)sender
{
if (reverse_search_mode && self.string.length) {
NSRange range = self.selectedRange;
range.length--;
self.string = [self.string substringWithRange:range];
if (range.length) {
[self selectAll:nil];
[self updateReverseSearch];
}
else {
reverse_search_mode = true;
current_line = [lines count] - 1;
}
}
else {
[super deleteBackward:sender];
}
}
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
{
reverse_search_mode = false;
auto_complete_context = 0;
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
}
- (BOOL)resignFirstResponder
{
reverse_search_mode = false;
return [super resignFirstResponder];
}
-(void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
if (reverse_search_mode && [super string].length == 0) {
NSMutableDictionary *attributes = [self.typingAttributes mutableCopy];
NSColor *color = [attributes[NSForegroundColorAttributeName] colorWithAlphaComponent:0.5];
[attributes setObject:color forKey:NSForegroundColorAttributeName];
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)];
}
}
/* Todo: lazy design, make it use a delegate instead of having a gb reference*/
- (void)insertTab:(id)sender
{
if (auto_complete_context == 0) {
NSRange selection = self.selectedRange;
if (selection.length) {
[self delete:nil];
}
auto_complete_range = NSMakeRange(selection.location, 0);
}
char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String);
uintptr_t context = auto_complete_context;
char *completion = GB_debugger_complete_substring(self.gb, substring, &context);
free(substring);
if (completion) {
NSString *ns_completion = @(completion);
free(completion);
if (!ns_completion) {
goto error;
}
self.selectedRange = auto_complete_range;
auto_complete_range.length = ns_completion.length;
[self replaceCharactersInRange:self.selectedRange withString:ns_completion];
auto_complete_context = context;
return;
}
error:
auto_complete_context = context;
NSBeep();
}
@end

View File

@ -1,31 +0,0 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
#import <JoyKit/JoyKit.h>
#import "GBOSDView.h"
@class Document;
typedef enum {
GB_FRAME_BLENDING_MODE_DISABLED,
GB_FRAME_BLENDING_MODE_SIMPLE,
GB_FRAME_BLENDING_MODE_ACCURATE,
GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE,
GB_FRAME_BLENDING_MODE_ACCURATE_ODD,
} GB_frame_blending_mode_t;
@interface GBView : NSView<JOYListener>
- (void) flip;
- (uint32_t *) pixels;
@property (nonatomic, weak) IBOutlet Document *document;
@property (nonatomic) GB_gameboy_t *gb;
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
@property (nonatomic, getter=isMouseHidingEnabled) bool mouseHidingEnabled;
@property (nonatomic) bool isRewinding;
@property (nonatomic, strong) NSView *internalView;
@property (weak) GBOSDView *osdView;
- (void) createInternalView;
- (uint32_t *)currentBuffer;
- (uint32_t *)previousBuffer;
- (void)screenSizeChanged;
- (void)setRumble: (double)amp;
- (NSImage *)renderToImage;
@end

View File

@ -1,686 +0,0 @@
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <Carbon/Carbon.h>
#import "GBView.h"
#import "GBViewGL.h"
#import "GBViewMetal.h"
#import "GBButtons.h"
#import "NSString+StringForKey.h"
#import "Document.h"
#define JOYSTICK_HIGH 0x4000
#define JOYSTICK_LOW 0x3800
static const uint8_t workboy_ascii_to_key[] = {
['0'] = GB_WORKBOY_0,
['`'] = GB_WORKBOY_UMLAUT,
['1'] = GB_WORKBOY_1,
['2'] = GB_WORKBOY_2,
['3'] = GB_WORKBOY_3,
['4'] = GB_WORKBOY_4,
['5'] = GB_WORKBOY_5,
['6'] = GB_WORKBOY_6,
['7'] = GB_WORKBOY_7,
['8'] = GB_WORKBOY_8,
['9'] = GB_WORKBOY_9,
['\r'] = GB_WORKBOY_ENTER,
[3] = GB_WORKBOY_ENTER,
['!'] = GB_WORKBOY_EXCLAMATION_MARK,
['$'] = GB_WORKBOY_DOLLAR,
['#'] = GB_WORKBOY_HASH,
['~'] = GB_WORKBOY_TILDE,
['*'] = GB_WORKBOY_ASTERISK,
['+'] = GB_WORKBOY_PLUS,
['-'] = GB_WORKBOY_MINUS,
['('] = GB_WORKBOY_LEFT_PARENTHESIS,
[')'] = GB_WORKBOY_RIGHT_PARENTHESIS,
[';'] = GB_WORKBOY_SEMICOLON,
[':'] = GB_WORKBOY_COLON,
['%'] = GB_WORKBOY_PERCENT,
['='] = GB_WORKBOY_EQUAL,
[','] = GB_WORKBOY_COMMA,
['<'] = GB_WORKBOY_LT,
['.'] = GB_WORKBOY_DOT,
['>'] = GB_WORKBOY_GT,
['/'] = GB_WORKBOY_SLASH,
['?'] = GB_WORKBOY_QUESTION_MARK,
[' '] = GB_WORKBOY_SPACE,
['\''] = GB_WORKBOY_QUOTE,
['@'] = GB_WORKBOY_AT,
['q'] = GB_WORKBOY_Q,
['w'] = GB_WORKBOY_W,
['e'] = GB_WORKBOY_E,
['r'] = GB_WORKBOY_R,
['t'] = GB_WORKBOY_T,
['y'] = GB_WORKBOY_Y,
['u'] = GB_WORKBOY_U,
['i'] = GB_WORKBOY_I,
['o'] = GB_WORKBOY_O,
['p'] = GB_WORKBOY_P,
['a'] = GB_WORKBOY_A,
['s'] = GB_WORKBOY_S,
['d'] = GB_WORKBOY_D,
['f'] = GB_WORKBOY_F,
['g'] = GB_WORKBOY_G,
['h'] = GB_WORKBOY_H,
['j'] = GB_WORKBOY_J,
['k'] = GB_WORKBOY_K,
['l'] = GB_WORKBOY_L,
['z'] = GB_WORKBOY_Z,
['x'] = GB_WORKBOY_X,
['c'] = GB_WORKBOY_C,
['v'] = GB_WORKBOY_V,
['b'] = GB_WORKBOY_B,
['n'] = GB_WORKBOY_N,
['m'] = GB_WORKBOY_M,
};
static const uint8_t workboy_vk_to_key[] = {
[kVK_F1] = GB_WORKBOY_CLOCK,
[kVK_F2] = GB_WORKBOY_TEMPERATURE,
[kVK_F3] = GB_WORKBOY_MONEY,
[kVK_F4] = GB_WORKBOY_CALCULATOR,
[kVK_F5] = GB_WORKBOY_DATE,
[kVK_F6] = GB_WORKBOY_CONVERSION,
[kVK_F7] = GB_WORKBOY_RECORD,
[kVK_F8] = GB_WORKBOY_WORLD,
[kVK_F9] = GB_WORKBOY_PHONE,
[kVK_F10] = GB_WORKBOY_UNKNOWN,
[kVK_Delete] = GB_WORKBOY_BACKSPACE,
[kVK_Shift] = GB_WORKBOY_SHIFT_DOWN,
[kVK_RightShift] = GB_WORKBOY_SHIFT_DOWN,
[kVK_UpArrow] = GB_WORKBOY_UP,
[kVK_DownArrow] = GB_WORKBOY_DOWN,
[kVK_LeftArrow] = GB_WORKBOY_LEFT,
[kVK_RightArrow] = GB_WORKBOY_RIGHT,
[kVK_Escape] = GB_WORKBOY_ESCAPE,
[kVK_ANSI_KeypadDecimal] = GB_WORKBOY_DECIMAL_POINT,
[kVK_ANSI_KeypadClear] = GB_WORKBOY_M,
[kVK_ANSI_KeypadMultiply] = GB_WORKBOY_H,
[kVK_ANSI_KeypadDivide] = GB_WORKBOY_J,
};
@implementation GBView
{
uint32_t *image_buffers[3];
unsigned char current_buffer;
bool mouse_hidden;
NSTrackingArea *tracking_area;
bool _mouseHidingEnabled;
bool axisActive[2];
bool underclockKeyDown;
double clockMultiplier;
double analogClockMultiplier;
bool analogClockMultiplierValid;
NSEventModifierFlags previousModifiers;
JOYController *lastController;
GB_frame_blending_mode_t _frameBlendingMode;
bool _turbo;
}
+ (instancetype)alloc
{
return [self allocWithZone:NULL];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
if (self == [GBView class]) {
if ([GBViewMetal isSupported]) {
return [GBViewMetal allocWithZone: zone];
}
return [GBViewGL allocWithZone: zone];
}
return [super allocWithZone:zone];
}
- (void) createInternalView
{
assert(false && "createInternalView must not be inherited");
}
- (void) _init
{
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil];
tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect
owner:self
userInfo:nil];
[self addTrackingArea:tracking_area];
clockMultiplier = 1.0;
[self createInternalView];
[self addSubview:self.internalView];
self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[JOYController registerListener:self];
}
- (void)screenSizeChanged
{
if (image_buffers[0]) free(image_buffers[0]);
if (image_buffers[1]) free(image_buffers[1]);
if (image_buffers[2]) free(image_buffers[2]);
size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(_gb) * GB_get_screen_height(_gb);
image_buffers[0] = calloc(1, buffer_size);
image_buffers[1] = calloc(1, buffer_size);
image_buffers[2] = calloc(1, buffer_size);
dispatch_async(dispatch_get_main_queue(), ^{
[self setFrame:self.superview.frame];
});
}
- (void) ratioKeepingChanged
{
[self setFrame:self.superview.frame];
}
- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode
{
_frameBlendingMode = frameBlendingMode;
[self setNeedsDisplay:true];
}
- (GB_frame_blending_mode_t)frameBlendingMode
{
if (_frameBlendingMode == GB_FRAME_BLENDING_MODE_ACCURATE) {
if (!_gb || GB_is_sgb(_gb)) {
return GB_FRAME_BLENDING_MODE_SIMPLE;
}
return GB_is_odd_frame(_gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN;
}
return _frameBlendingMode;
}
- (unsigned char) numberOfBuffers
{
return _frameBlendingMode? 3 : 2;
}
- (void)dealloc
{
free(image_buffers[0]);
free(image_buffers[1]);
free(image_buffers[2]);
if (mouse_hidden) {
mouse_hidden = false;
[NSCursor unhide];
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self setRumble:0];
[JOYController unregisterListener:self];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
if (!(self = [super initWithCoder:coder])) {
return self;
}
[self _init];
return self;
}
- (instancetype)initWithFrame:(NSRect)frameRect
{
if (!(self = [super initWithFrame:frameRect])) {
return self;
}
[self _init];
return self;
}
- (void)setFrame:(NSRect)frame
{
frame = self.superview.frame;
if (_gb && ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) {
double ratio = frame.size.width / frame.size.height;
double width = GB_get_screen_width(_gb);
double height = GB_get_screen_height(_gb);
if (ratio >= width / height) {
double new_width = round(frame.size.height / height * width);
frame.origin.x = floor((frame.size.width - new_width) / 2);
frame.size.width = new_width;
frame.origin.y = 0;
}
else {
double new_height = round(frame.size.width / width * height);
frame.origin.y = floor((frame.size.height - new_height) / 2);
frame.size.height = new_height;
frame.origin.x = 0;
}
}
[super setFrame:frame];
}
- (void) flip
{
if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) {
clockMultiplier = 1.0;
GB_set_clock_multiplier(_gb, analogClockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier);
}
if (analogClockMultiplier == 1.0) {
analogClockMultiplierValid = false;
}
if (analogClockMultiplier < 2.0 && analogClockMultiplier > 1.0) {
GB_set_turbo_mode(_gb, false, false);
if (self.document.partner) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
}
}
else {
if (underclockKeyDown && clockMultiplier > 0.5) {
clockMultiplier -= 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
}
}
if (!underclockKeyDown && clockMultiplier < 1.0) {
clockMultiplier += 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
}
}
}
if ((!analogClockMultiplierValid && clockMultiplier > 1) ||
_turbo || (analogClockMultiplierValid && analogClockMultiplier > 1)) {
[self.osdView displayText:@"Fast forwarding..."];
}
else if ((!analogClockMultiplierValid && clockMultiplier < 1) ||
(analogClockMultiplierValid && analogClockMultiplier < 1)) {
[self.osdView displayText:@"Slow motion..."];
}
current_buffer = (current_buffer + 1) % self.numberOfBuffers;
}
- (uint32_t *) pixels
{
return image_buffers[(current_buffer + 1) % self.numberOfBuffers];
}
-(void)keyDown:(NSEvent *)theEvent
{
if ([theEvent type] != NSEventTypeFlagsChanged && theEvent.isARepeat) return;
unsigned short keyCode = theEvent.keyCode;
if (GB_workboy_is_enabled(_gb)) {
if (theEvent.keyCode < sizeof(workboy_vk_to_key) && workboy_vk_to_key[theEvent.keyCode]) {
GB_workboy_set_key(_gb, workboy_vk_to_key[theEvent.keyCode]);
return;
}
unichar c = [theEvent type] != NSEventTypeFlagsChanged? [theEvent.charactersIgnoringModifiers.lowercaseString characterAtIndex:0] : 0;
if (c < sizeof(workboy_ascii_to_key) && workboy_ascii_to_key[c]) {
GB_workboy_set_key(_gb, workboy_ascii_to_key[c]);
return;
}
}
bool handled = false;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
for (unsigned player = 0; player < player_count; player++) {
for (GBButton button = 0; button < GBButtonCount; button++) {
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
if (!key) continue;
if (key.unsignedShortValue == keyCode) {
handled = true;
switch (button) {
case GBTurbo:
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, true, false);
}
else {
GB_set_turbo_mode(_gb, true, self.isRewinding);
}
_turbo = true;
analogClockMultiplierValid = false;
break;
case GBRewind:
if (!self.document.partner) {
self.isRewinding = true;
GB_set_turbo_mode(_gb, false, false);
_turbo = false;
}
break;
case GBUnderclock:
underclockKeyDown = true;
analogClockMultiplierValid = false;
break;
default:
if (self.document.partner) {
if (player == 0) {
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, true);
}
else {
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, true);
}
}
else {
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
}
break;
}
}
}
}
if (!handled && [theEvent type] != NSEventTypeFlagsChanged) {
[super keyDown:theEvent];
}
}
-(void)keyUp:(NSEvent *)theEvent
{
unsigned short keyCode = theEvent.keyCode;
if (GB_workboy_is_enabled(_gb)) {
if (keyCode == kVK_Shift || keyCode == kVK_RightShift) {
GB_workboy_set_key(_gb, GB_WORKBOY_SHIFT_UP);
}
else {
GB_workboy_set_key(_gb, GB_WORKBOY_NONE);
}
}
bool handled = false;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
for (unsigned player = 0; player < player_count; player++) {
for (GBButton button = 0; button < GBButtonCount; button++) {
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
if (!key) continue;
if (key.unsignedShortValue == keyCode) {
handled = true;
switch (button) {
case GBTurbo:
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
else {
GB_set_turbo_mode(_gb, false, false);
}
_turbo = false;
analogClockMultiplierValid = false;
break;
case GBRewind:
self.isRewinding = false;
break;
case GBUnderclock:
underclockKeyDown = false;
analogClockMultiplierValid = false;
break;
default:
if (self.document.partner) {
if (player == 0) {
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, false);
}
else {
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, false);
}
}
else {
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
}
break;
}
}
}
}
if (!handled && [theEvent type] != NSEventTypeFlagsChanged) {
[super keyUp:theEvent];
}
}
- (void)setRumble:(double)amp
{
[lastController setRumbleAmplitude:amp];
}
- (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis
{
if (![self.window isMainWindow]) return;
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
if (!mapping) {
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
}
if ((axis.usage == JOYAxisUsageR1 && !mapping) ||
axis.uniqueID == [mapping[@"AnalogUnderclock"] unsignedLongValue]){
analogClockMultiplier = MIN(MAX(1 - axis.value + 0.05, 1.0 / 3), 1.0);
analogClockMultiplierValid = true;
}
else if ((axis.usage == JOYAxisUsageL1 && !mapping) ||
axis.uniqueID == [mapping[@"AnalogTurbo"] unsignedLongValue]){
analogClockMultiplier = MIN(MAX(axis.value * 3 + 0.95, 1.0), 3.0);
analogClockMultiplierValid = true;
}
}
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
{
if (![self.window isMainWindow]) return;
unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
IOPMAssertionID assertionID;
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
for (unsigned player = 0; player < player_count; player++) {
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"]
objectForKey:n2s(player)];
if (player_count != 1 && // Single player, accpet inputs from all joypads
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
![preferred_joypad isEqualToString:controller.uniqueID]) {
continue;
}
dispatch_async(dispatch_get_main_queue(), ^{
[controller setPlayerLEDs:[controller LEDMaskForPlayer:player]];
});
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
if (!mapping) {
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
}
JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: button.usage;
if (!mapping && usage >= JOYButtonUsageGeneric0) {
usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3];
}
GB_gameboy_t *effectiveGB = _gb;
unsigned effectivePlayer = player;
if (player && self.document.partner) {
effectiveGB = self.document.partner.gb;
effectivePlayer = 0;
if (controller != self.document.partner.view->lastController) {
[self setRumble:0];
self.document.partner.view->lastController = controller;
}
}
else {
if (controller != lastController) {
[self setRumble:0];
lastController = controller;
}
}
switch (usage) {
case JOYButtonUsageNone: break;
case JOYButtonUsageA: GB_set_key_state_for_player(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break;
case JOYButtonUsageB: GB_set_key_state_for_player(effectiveGB, GB_KEY_B, effectivePlayer, button.isPressed); break;
case JOYButtonUsageC: break;
case JOYButtonUsageStart:
case JOYButtonUsageX: GB_set_key_state_for_player(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break;
case JOYButtonUsageSelect:
case JOYButtonUsageY: GB_set_key_state_for_player(effectiveGB, GB_KEY_SELECT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageR2:
case JOYButtonUsageL2:
case JOYButtonUsageZ: {
self.isRewinding = button.isPressed;
if (button.isPressed) {
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
else {
GB_set_turbo_mode(_gb, false, false);
}
_turbo = false;
}
break;
}
case JOYButtonUsageL1: {
if (!analogClockMultiplierValid || analogClockMultiplier == 1.0 || !button.isPressed) {
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false);
}
else {
GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding);
}
_turbo = button.isPressed;
}
break;
}
case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break;
case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(effectiveGB, GB_KEY_LEFT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadRight: GB_set_key_state_for_player(effectiveGB, GB_KEY_RIGHT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadUp: GB_set_key_state_for_player(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break;
default:
break;
}
}
}
- (BOOL)acceptsFirstResponder
{
return true;
}
- (void)mouseEntered:(NSEvent *)theEvent
{
if (!mouse_hidden) {
mouse_hidden = true;
if (_mouseHidingEnabled) {
[NSCursor hide];
}
}
[super mouseEntered:theEvent];
}
- (void)mouseExited:(NSEvent *)theEvent
{
if (mouse_hidden) {
mouse_hidden = false;
if (_mouseHidingEnabled) {
[NSCursor unhide];
}
}
[super mouseExited:theEvent];
}
- (void)setMouseHidingEnabled:(bool)mouseHidingEnabled
{
if (mouseHidingEnabled == _mouseHidingEnabled) return;
_mouseHidingEnabled = mouseHidingEnabled;
if (mouse_hidden && _mouseHidingEnabled) {
[NSCursor hide];
}
if (mouse_hidden && !_mouseHidingEnabled) {
[NSCursor unhide];
}
}
- (bool)isMouseHidingEnabled
{
return _mouseHidingEnabled;
}
- (void) flagsChanged:(NSEvent *)event
{
if (event.modifierFlags > previousModifiers) {
[self keyDown:event];
}
else {
[self keyUp:event];
}
previousModifiers = event.modifierFlags;
}
- (uint32_t *)currentBuffer
{
return image_buffers[current_buffer];
}
- (uint32_t *)previousBuffer
{
return image_buffers[(current_buffer + 2) % self.numberOfBuffers];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
if (GB_is_save_state(fileURL.fileSystemRepresentation)) {
return NSDragOperationGeneric;
}
}
return NSDragOperationNone;
}
-(BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
return [_document loadStateFile:fileURL.fileSystemRepresentation noErrorOnNotFound:false];
}
return false;
}
- (NSImage *)renderToImage;
{
/* Not going to support this on OpenGL, OpenGL is too much of a terrible API for me
to bother figuring out how the hell something so trivial can be done. */
return nil;
}
@end

View File

@ -1,5 +0,0 @@
#import "GBView.h"
@interface GBViewGL : GBView
@end

View File

@ -1,35 +0,0 @@
#import "GBViewGL.h"
#import "GBOpenGLView.h"
@implementation GBViewGL
- (void)createInternalView
{
NSOpenGLPixelFormatAttribute attrs[] =
{
NSOpenGLPFAOpenGLProfile,
NSOpenGLProfileVersion3_2Core,
0
};
NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
assert(pf);
NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil];
self.internalView = [[GBOpenGLView alloc] initWithFrame:self.frame pixelFormat:pf];
((GBOpenGLView *)self.internalView).wantsBestResolutionOpenGLSurface = true;
((GBOpenGLView *)self.internalView).openGLContext = context;
}
- (void)flip
{
[super flip];
dispatch_async(dispatch_get_main_queue(), ^{
[self.internalView setNeedsDisplay:true];
[self setNeedsDisplay:true];
});
}
@end

View File

@ -1,7 +0,0 @@
#import <Cocoa/Cocoa.h>
#import <MetalKit/MetalKit.h>
#import "GBView.h"
@interface GBViewMetal : GBView<MTKViewDelegate>
+ (bool) isSupported;
@end

View File

@ -1,232 +0,0 @@
#import <CoreImage/CoreImage.h>
#import "GBViewMetal.h"
#pragma clang diagnostic ignored "-Wpartial-availability"
static const vector_float2 rect[] =
{
{-1, -1},
{ 1, -1},
{-1, 1},
{ 1, 1},
};
@implementation GBViewMetal
{
id<MTLDevice> device;
id<MTLTexture> texture, previous_texture;
id<MTLBuffer> vertices;
id<MTLRenderPipelineState> pipeline_state;
id<MTLCommandQueue> command_queue;
id<MTLBuffer> frame_blending_mode_buffer;
id<MTLBuffer> output_resolution_buffer;
vector_float2 output_resolution;
}
+ (bool)isSupported
{
if (MTLCopyAllDevices) {
return [MTLCopyAllDevices() count];
}
return false;
}
- (void) allocateTextures
{
if (!device) return;
MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init];
texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
texture_descriptor.width = GB_get_screen_width(self.gb);
texture_descriptor.height = GB_get_screen_height(self.gb);
texture = [device newTextureWithDescriptor:texture_descriptor];
previous_texture = [device newTextureWithDescriptor:texture_descriptor];
}
- (void)createInternalView
{
MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())];
view.delegate = self;
self.internalView = view;
view.paused = true;
view.enableSetNeedsDisplay = true;
view.framebufferOnly = false;
vertices = [device newBufferWithBytes:rect
length:sizeof(rect)
options:MTLResourceStorageModeShared];
static const GB_frame_blending_mode_t default_blending_mode = GB_FRAME_BLENDING_MODE_DISABLED;
frame_blending_mode_buffer = [device newBufferWithBytes:&default_blending_mode
length:sizeof(default_blending_mode)
options:MTLResourceStorageModeShared];
output_resolution_buffer = [device newBufferWithBytes:&output_resolution
length:sizeof(output_resolution)
options:MTLResourceStorageModeShared];
output_resolution = (simd_float2){view.drawableSize.width, view.drawableSize.height};
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil];
[self loadShader];
}
- (void) loadShader
{
NSError *error = nil;
NSString *shader_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"MasterShader"
ofType:@"metal"
inDirectory:@"Shaders"]
encoding:NSUTF8StringEncoding
error:nil];
NSString *shader_name = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
NSString *scaler_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:shader_name
ofType:@"fsh"
inDirectory:@"Shaders"]
encoding:NSUTF8StringEncoding
error:nil];
shader_source = [shader_source stringByReplacingOccurrencesOfString:@"{filter}"
withString:scaler_source];
MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
options.fastMathEnabled = true;
id<MTLLibrary> library = [device newLibraryWithSource:shader_source
options:options
error:&error];
if (error) {
NSLog(@"Error: %@", error);
if (!library) {
return;
}
}
id<MTLFunction> vertex_function = [library newFunctionWithName:@"vertex_shader"];
id<MTLFunction> fragment_function = [library newFunctionWithName:@"fragment_shader"];
// Set up a descriptor for creating a pipeline state object
MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipeline_state_descriptor.vertexFunction = vertex_function;
pipeline_state_descriptor.fragmentFunction = fragment_function;
pipeline_state_descriptor.colorAttachments[0].pixelFormat = ((MTKView *)self.internalView).colorPixelFormat;
error = nil;
pipeline_state = [device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
error:&error];
if (error) {
NSLog(@"Failed to created pipeline state, error %@", error);
return;
}
command_queue = [device newCommandQueue];
}
- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
{
output_resolution = (vector_float2){size.width, size.height};
dispatch_async(dispatch_get_main_queue(), ^{
[(MTKView *)self.internalView draw];
});
}
- (void)drawInMTKView:(MTKView *)view
{
if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
if (!self.gb) return;
if (texture.width != GB_get_screen_width(self.gb) ||
texture.height != GB_get_screen_height(self.gb)) {
[self allocateTextures];
}
MTLRegion region = {
{0, 0, 0}, // MTLOrigin
{texture.width, texture.height, 1} // MTLSize
};
[texture replaceRegion:region
mipmapLevel:0
withBytes:[self currentBuffer]
bytesPerRow:texture.width * 4];
if ([self frameBlendingMode]) {
[previous_texture replaceRegion:region
mipmapLevel:0
withBytes:[self previousBuffer]
bytesPerRow:texture.width * 4];
}
MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
if (render_pass_descriptor != nil) {
*(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode];
*(vector_float2 *)[output_resolution_buffer contents] = output_resolution;
id<MTLRenderCommandEncoder> render_encoder =
[command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor];
[render_encoder setViewport:(MTLViewport){0.0, 0.0,
output_resolution.x,
output_resolution.y,
-1.0, 1.0}];
[render_encoder setRenderPipelineState:pipeline_state];
[render_encoder setVertexBuffer:vertices
offset:0
atIndex:0];
[render_encoder setFragmentBuffer:frame_blending_mode_buffer
offset:0
atIndex:0];
[render_encoder setFragmentBuffer:output_resolution_buffer
offset:0
atIndex:1];
[render_encoder setFragmentTexture:texture
atIndex:0];
[render_encoder setFragmentTexture:previous_texture
atIndex:1];
[render_encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
vertexStart:0
vertexCount:4];
[render_encoder endEncoding];
[command_buffer presentDrawable:view.currentDrawable];
}
[command_buffer commit];
}
- (void)flip
{
[super flip];
dispatch_async(dispatch_get_main_queue(), ^{
[(MTKView *)self.internalView setNeedsDisplay:true];
});
}
- (NSImage *)renderToImage
{
CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture]
options:@{
kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB()
}];
ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1),
0, ciImage.extent.size.height)];
CIContext *context = [CIContext context];
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size];
CGImageRelease(cgImage);
return ret;
}
@end

View File

@ -1,6 +0,0 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
@interface GBVisualizerView : NSView
- (void)addSample:(GB_sample_t *)sample;
@end

View File

@ -1,87 +0,0 @@
#import "GBVisualizerView.h"
#include <Core/gb.h>
#define SAMPLE_COUNT 1024
static NSColor *color_to_effect_color(typeof(GB_PALETTE_DMG.colors[0]) color)
{
if (@available(macOS 10.10, *)) {
double tint = MAX(color.r, MAX(color.g, color.b)) + 64;
return [NSColor colorWithRed:color.r / tint
green:color.g / tint
blue:color.b / tint
alpha:tint/(255 + 64)];
}
return [NSColor colorWithRed:color.r / 255.0
green:color.g / 255.0
blue:color.b / 255.0
alpha:1.0];
}
@implementation GBVisualizerView
{
GB_sample_t _samples[SAMPLE_COUNT];
size_t _position;
}
- (void)drawRect:(NSRect)dirtyRect
{
const GB_palette_t *palette;
switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]) {
case 1:
palette = &GB_PALETTE_DMG;
break;
case 2:
palette = &GB_PALETTE_MGB;
break;
case 3:
palette = &GB_PALETTE_GBL;
break;
default:
palette = &GB_PALETTE_GREY;
break;
}
NSSize size = self.bounds.size;
[color_to_effect_color(palette->colors[0]) setFill];
NSRectFill(self.bounds);
NSBezierPath *line = [NSBezierPath bezierPath];
[line moveToPoint:NSMakePoint(0, size.height / 2)];
for (unsigned i = 0; i < SAMPLE_COUNT; i++) {
GB_sample_t *sample = _samples + ((i + _position) % SAMPLE_COUNT);
double volume = ((signed)sample->left + (signed)sample->right) / 32768.0;
[line lineToPoint:NSMakePoint(size.width * (i + 0.5) / SAMPLE_COUNT,
(volume + 1) * size.height / 2)];
}
[line lineToPoint:NSMakePoint(size.width, size.height / 2)];
[line setLineWidth:1.0];
[color_to_effect_color(palette->colors[2]) setFill];
[line fill];
[color_to_effect_color(palette->colors[1]) setFill];
NSRectFill(NSMakeRect(0, size.height / 2 - 0.5, size.width, 1));
[color_to_effect_color(palette->colors[3]) setStroke];
[line stroke];
[super drawRect:dirtyRect];
}
- (void)addSample:(GB_sample_t *)sample
{
_samples[_position++] = *sample;
if (_position == SAMPLE_COUNT) {
_position = 0;
}
}
@end

View File

@ -1,8 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBWarningPopover : NSPopover
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view;
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window;
@end

View File

@ -1,46 +0,0 @@
#import "GBWarningPopover.h"
static GBWarningPopover *lastPopover;
@implementation GBWarningPopover
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view
{
[lastPopover close];
lastPopover = [[self alloc] init];
[lastPopover setBehavior:NSPopoverBehaviorApplicationDefined];
[lastPopover setAnimates:true];
lastPopover.contentViewController = [[NSViewController alloc] initWithNibName:@"PopoverView" bundle:nil];
NSTextField *field = (NSTextField *)lastPopover.contentViewController.view;
[field setStringValue:contents];
NSSize textSize = [field.cell cellSizeForBounds:[field.cell drawingRectForBounds:NSMakeRect(0, 0, 240, CGFLOAT_MAX)]];
textSize.width = ceil(textSize.width) + 16;
textSize.height = ceil(textSize.height) + 12;
[lastPopover setContentSize:textSize];
if (!view.window.isVisible) {
[view.window setIsVisible:true];
}
[lastPopover showRelativeToRect:view.bounds
ofView:view
preferredEdge:NSMinYEdge];
NSRect frame = field.frame;
frame.origin.x += 8;
frame.origin.y -= 6;
field.frame = frame;
[lastPopover performSelector:@selector(close) withObject:nil afterDelay:3.0];
return lastPopover;
}
+ (GBWarningPopover *)popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window
{
return [self popoverWithContents:contents onView:window.contentView.superview.subviews.lastObject];
}
@end

View File

@ -1,204 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>SameBoy</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gb</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Cartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy Game</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.github.liji32.sameboy.gb</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gbc</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy Color Game</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.github.liji32.sameboy.gbc</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>isx</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy ISX File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.github.liji32.sameboy.isx</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gbs</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy Sound File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.github.liji32.sameboy.gbs</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>SameBoy</string>
<key>CFBundleIconFile</key>
<string>AppIcon.icns</string>
<key>CFBundleIdentifier</key>
<string>com.github.liji32.sameboy</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>SameBoy</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>Version @VERSION</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSMinimumSystemVersion</key>
<string>10.9</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015-2021 Lior Halphon</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Game Boy Game</string>
<key>UTTypeIconFile</key>
<string>Cartridge</string>
<key>UTTypeIdentifier</key>
<string>com.github.liji32.sameboy.gb</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gb</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Game Boy Color Game</string>
<key>UTTypeIconFile</key>
<string>ColorCartridge</string>
<key>UTTypeIdentifier</key>
<string>com.github.liji32.sameboy.gbc</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gbc</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Game Boy ISX File</string>
<key>UTTypeIconFile</key>
<string>ColorCartridge</string>
<key>UTTypeIdentifier</key>
<string>com.github.liji32.sameboy.isx</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>isx</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Game Boy Sound File</string>
<key>UTTypeIconFile</key>
<string>ColorCartridge</string>
<key>UTTypeIdentifier</key>
<string>com.github.liji32.sameboy.gbs</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gbs</string>
</array>
</dict>
</dict>
</array>
<key>NSCameraUsageDescription</key>
<string>SameBoy needs to access your camera to emulate the Game Boy Camera</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

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