Compare commits
No commits in common. "v008" and "master" have entirely different histories.
BIN
.assets/bahamut-lagoon.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
.assets/tengai-makyou-zero.png
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
.assets/user-interface.png
Normal file
After Width: | Height: | Size: 46 KiB |
23
.cirrus.yml
Normal file
|
@ -0,0 +1,23 @@
|
|||
freebsd-x86_64-binaries_task:
|
||||
freebsd_instance:
|
||||
image_family: freebsd-12-2
|
||||
|
||||
setup_script:
|
||||
- pkg install --yes gmake gdb gcc8 pkgconf sdl2 openal-soft gtk3 gtksourceview3 libXv zip
|
||||
|
||||
compile_script:
|
||||
- gmake -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
|
||||
- cp -a extras/* bsnes-nightly
|
||||
- zip -r bsnes-nightly.zip bsnes-nightly
|
||||
|
||||
bsnes-nightly_artifacts:
|
||||
path: "bsnes-nightly.zip"
|
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.fs linguist-detectable=false
|
||||
*.vs linguist-detectable=false
|
139
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
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
Normal file
|
@ -0,0 +1 @@
|
|||
hiro/qt/qt.moc
|
59
CREDITS.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
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!
|
||||
|
674
GPLv3.txt
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
43
LICENSE.txt
Normal file
|
@ -0,0 +1,43 @@
|
|||
----------------------------------------------------------------------
|
||||
bsnes - Super Nintendo emulator
|
||||
|
||||
Copyright © 2004-2020 byuu et al
|
||||
|
||||
https://byuu.org/bsnes/
|
||||
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
libco - C cooperative threading library
|
||||
nall - C++ template library
|
||||
ruby - Hardware abstraction library
|
||||
hiro - User interface library
|
||||
|
||||
Copyright © 2006-2020 byuu et al
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
above copyright notice and this permission notice appear in all
|
||||
copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||||
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
----------------------------------------------------------------------
|
75
README.md
Normal file
|
@ -0,0 +1,75 @@
|
|||
bsnes
|
||||
=====
|
||||
|
||||

|
||||
|
||||
bsnes is a multi-platform Super Nintendo (Super Famicom) emulator, originally
|
||||
developed by Near, which focuses on performance,
|
||||
features, and ease of use.
|
||||
|
||||
Unique Features
|
||||
---------------
|
||||
|
||||
- True Super Game Boy emulation (using the SameBoy core by Lior Halphon)
|
||||
- HD mode 7 graphics with optional supersampling (by DerKoun)
|
||||
- Low-level emulation of all SNES coprocessors (DSP-n, ST-01n, Cx4)
|
||||
- Multi-threaded PPU graphics renderer
|
||||
- Speed mode settings which retain smooth audio output (50%, 75%, 100%, 150%, 200%)
|
||||
- Built-in games database with thousands of game entries
|
||||
- Built-in cheat code database for hundreds of popular games (by mightymo)
|
||||
- Built-in save state manager with screenshot previews and naming capabilities
|
||||
- Customizable per-byte game mappings to support any cartridges, including prototype games
|
||||
- 7-zip decompression support
|
||||
- Extensive Satellaview emulation, including BS Memory flash write and wear-leveling emulation
|
||||
- Optional higan game folder support (standard game ROM files are also fully supported!)
|
||||
- Advanced mapping system allowing multiple bindings to every emulated input
|
||||
|
||||
Standard Features
|
||||
-----------------
|
||||
|
||||
- MSU1 support
|
||||
- BPS and IPS soft-patching support
|
||||
- Save states with undo and redo support (for reverting accidental saves and loads)
|
||||
- OpenGL multi-pass pixel shaders
|
||||
- Several built-in software filters, including HQ2x (by MaxSt) and snes_ntsc (by blargg)
|
||||
- Adaptive sync and dynamic rate control for perfect audio/video synchronization
|
||||
- Just-in-time input polling for minimal input latency
|
||||
- Run-ahead support for removing internal game engine input latency
|
||||
- Support for Direct3D exclusive mode video
|
||||
- Support for WASAPI exclusive mode audio
|
||||
- Periodic auto-saving of game saves
|
||||
- Auto-saving of states when unloading games, and auto-resuming of states when reloading games
|
||||
- Sprite limit disable support
|
||||
- Cubic audio interpolation support
|
||||
- Optional high-level emulation of most SNES coprocessors
|
||||
- Optional emulation of flaws in older emulators for compatibility with older unofficial software
|
||||
- CPU, SA1, and SuperFX overclocking support
|
||||
- Frame advance support
|
||||
- Screenshot support
|
||||
- Cheat code search support
|
||||
- Movie recording and playback support
|
||||
- Rewind support
|
||||
- HiDPI support
|
||||
- Multi-monitor support
|
||||
- Turbo support for controller inputs
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
- [Official git repository](https://github.com/bsnes-emu/bsnes)
|
||||
- [Official Discord](https://discord.gg/B27hf27ZVf)
|
||||
|
||||
Nightly Builds
|
||||
--------------
|
||||
|
||||
- [Windows](https://github.com/bsnes-emu/bsnes/releases/download/nightly/bsnes-windows.zip)
|
||||
- [macOS](https://github.com/bsnes-emu/bsnes/releases/download/nightly/bsnes-macos.zip)
|
||||
- [Linux](https://github.com/bsnes-emu/bsnes/releases/download/nightly/bsnes-ubuntu.zip)
|
||||
- [FreeBSD](https://api.cirrus-ci.com/v1/artifact/github/bsnes-emu/bsnes/freebsd-x86_64-binaries/bsnes-nightly/bsnes-nightly.zip)
|
||||
|
||||
Preview
|
||||
-------
|
||||
|
||||

|
||||

|
||||

|
27
bsnes.cfg
|
@ -1,27 +0,0 @@
|
|||
#[bsnes v0.007a configuration file]
|
||||
|
||||
#[video mode]
|
||||
# 0: 256x224w
|
||||
# 1: 512x448w
|
||||
# 2: 960x720w
|
||||
video.mode = 1
|
||||
|
||||
#[video memory type]
|
||||
# true: video ram (VRAM)
|
||||
# false: system ram (SRAM)
|
||||
#
|
||||
# VRAM results in the image being stretched in hardware,
|
||||
# which is generally much faster, and automatically adds
|
||||
# bilinear filtering (if the card supports it).
|
||||
#
|
||||
# However, some video cards end up taking a major speed
|
||||
# loss when this option is enabled. It is also the only
|
||||
# way to guarantee that the output image will not be
|
||||
# filtered.
|
||||
video.use_vram = true
|
||||
#video.use_vram = false
|
||||
|
||||
#[show fps]
|
||||
# true: show fps in titlebar
|
||||
# false: do not show fps in titlebar
|
||||
#gui.show_fps = false
|
44
bsnes/Database/BS Memory.bml
Normal file
|
@ -0,0 +1,44 @@
|
|||
database
|
||||
revision: 2020-01-01
|
||||
|
||||
//BS Memory (JPN)
|
||||
|
||||
database
|
||||
revision: 2018-04-14
|
||||
|
||||
game
|
||||
sha256: 80c34b50817d58820bc8c88d2d9fa462550b4a76372e19c6467cbfbc8cf5d9ef
|
||||
label: 鮫亀 キャラカセット
|
||||
name: Same Game - Chara Cassette
|
||||
region: BSMC-ZS5J-JPN
|
||||
revision: BSMC-ZS5J-0
|
||||
board: BSMC-CR-01
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
|
||||
game
|
||||
sha256: 859c7f7b4771d920a5bdb11f1d247ab6b43fb026594d1062f6f72d32cd340a0a
|
||||
label: 鮫亀 キャラデータ集
|
||||
name: Same Game - Chara Data Shuu
|
||||
region: BSMC-YS5J-JPN
|
||||
revision: BSMC-YS5J-0
|
||||
board: BSMC-CR-01
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
|
||||
game
|
||||
sha256: c92a15fdd9b0133f9ea69105d0230a3acd1cdeef98567462eca86ea02a959e4e
|
||||
label: SDガンダム ジーネクスト ユニット&マップコレクション
|
||||
name: SD Gundam G Next - Unit & Map Collection
|
||||
region: BSMC-ZX3J-JPN
|
||||
revision: BSMC-ZX3J-0
|
||||
board: BSMC-BR-01
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
|
87497
bsnes/Database/Cheat Codes.bml
Normal file
213
bsnes/Database/Sufami Turbo.bml
Normal file
|
@ -0,0 +1,213 @@
|
|||
database
|
||||
revision: 2020-01-01
|
||||
|
||||
//Sufami Turbo (JPN)
|
||||
|
||||
database
|
||||
revision: 2018-04-14
|
||||
|
||||
game
|
||||
sha256: f73bda08743565e0bd101632ebbac2d363d703a3ab39d23f49d95217cab29269
|
||||
label: 美少女戦士セーラームーン セーラースターズ ふわふわパニック2
|
||||
name: Bishoujo Senshi Sailor Moon Sailor Stars - Fuwafuwa Panic 2
|
||||
region: SFT-0112-JPN
|
||||
revision: SAILOR MOON
|
||||
board: PT-911
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x100000
|
||||
content: Program
|
||||
note: Unlinkable
|
||||
|
||||
game
|
||||
sha256: afb3f2a83b5bfcb1b8829b6995f108cc4d64ca322d1ba4a50b83af6e1f2e89bd
|
||||
label: クレヨンしんちゃん 長ぐつドボン!!
|
||||
name: Crayon Shin-chan - Nagagutsu Dobon!!
|
||||
region: SFT-0113-JPN
|
||||
revision: SHINCYAN
|
||||
board: PT-911
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
note: Unlinkable
|
||||
|
||||
game
|
||||
sha256: d93b3a570e7cf343f680ab0768a50b77e3577f9c555007e2de3decd6bc4765c8
|
||||
label: ゲゲゲの鬼太郎 妖怪ドンジャラ
|
||||
name: Gegege no Kitarou - Youkai Donjara
|
||||
region: SFT-0106-JPN
|
||||
revision: KITARO DONJYAR
|
||||
board: PT-911
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
note: Unlinkable
|
||||
|
||||
game
|
||||
sha256: 89aecd4e23d8219c8de3e71bb75db3dfe667d51c1eba4ea7239d2f772743d0cc
|
||||
label: 激走戦隊カーレンジャ 全開!レーサー戦士
|
||||
name: Gekisou Sentai Carranger - Zenkai! Racer Senshi
|
||||
region: SFT-0109-JPN
|
||||
revision: CAR RANGER
|
||||
board: PT-911
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
note: Unlinkable
|
||||
|
||||
game
|
||||
sha256: 602b20b788640f5743487108a10f3f77bca5ce2d24208b25b1ca498a96eb0d69
|
||||
label: ぽいぽい忍者ワールド
|
||||
name: Poi Poi Ninja World
|
||||
region: SFT-0103-JPN
|
||||
revision: POI POI NINJYA
|
||||
board: PT-911
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x800
|
||||
content: Save
|
||||
note: Linkable: SFT-0103-JPN
|
||||
|
||||
game
|
||||
sha256: 2a9d7c9a61318861028a73ca03e32a48cff162d76cba36fbaab8690b212efe9b
|
||||
label: SDガンダムジェネレーション アクシズ戦記
|
||||
name: SD Gundam Generation - Axis Senki
|
||||
region: SFT-0107-JPN
|
||||
revision: GUNDAM C
|
||||
board: PT-912
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x2000
|
||||
content: Save
|
||||
note: Linkable: SFT-0104-JPN, SFT-0105-JPN, SFT-0107-JPN, SFT-0108-JPN, SFT-0110-JPN, SFT-0111-JPN
|
||||
|
||||
game
|
||||
sha256: 60ac017c18f534e8cf24ca7f38e22ce92db95ea6c30b2d59d76f13c4f1c8a6e4
|
||||
label: SDガンダムジェネレーション バビロニア建国戦記
|
||||
name: SD Gundam Generation - Babylonia Kenkoku Senki
|
||||
region: SFT-0108-JPN
|
||||
revision: GUNDAM D
|
||||
board: PT-912
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x2000
|
||||
content: Save
|
||||
note: Linkable: SFT-0104-JPN, SFT-0105-JPN, SFT-0107-JPN, SFT-0108-JPN, SFT-0110-JPN, SFT-0111-JPN
|
||||
|
||||
game
|
||||
sha256: e639b5d5d722432b6809ccc6801dc584e1a3016379f34b335ed2dfa73b1ebf69
|
||||
label: SDガンダムジェネレーション コロニー格闘記
|
||||
name: SD Gundam Generation - Colony Kakutouki
|
||||
region: SFT-0111-JPN
|
||||
revision: GUNDAM F
|
||||
board: PT-912
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x2000
|
||||
content: Save
|
||||
note: Linkable: SFT-0104-JPN, SFT-0105-JPN, SFT-0107-JPN, SFT-0108-JPN, SFT-0110-JPN, SFT-0111-JPN
|
||||
|
||||
game
|
||||
sha256: 8547a08ed11fe408eac282a90ac46654bd2e5f49bda3aec8e5edf166a0a4b9af
|
||||
label: SDガンダムジェネレーション グリプス戦記
|
||||
name: SD Gundam Generation - Gryps Senki
|
||||
region: SFT-0105-JPN
|
||||
revision: GUNDAM B
|
||||
board: PT-912
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x2000
|
||||
content: Save
|
||||
note: Linkable: SFT-0104-JPN, SFT-0105-JPN, SFT-0107-JPN, SFT-0108-JPN, SFT-0110-JPN, SFT-0111-JPN
|
||||
|
||||
game
|
||||
sha256: 3e82215bed08274874b30d461fc4a965c6bca932229da5d46d56e36f484d65eb
|
||||
label: SDガンダムジェネレーション 一年戦争記
|
||||
name: SD Gundam Generation - Ichinen Sensouki
|
||||
region: SFT-0104-JPN
|
||||
revision: GUNDAM A
|
||||
board: PT-912
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x2000
|
||||
content: Save
|
||||
note: Linkable: SFT-0104-JPN, SFT-0105-JPN, SFT-0107-JPN, SFT-0108-JPN, SFT-0110-JPN, SFT-0111-JPN
|
||||
|
||||
game
|
||||
sha256: 5951a58a91d8e397d0a237ccc2b1248e17c7312cb9cc11cbc350200a97b4e021
|
||||
label: SDガンダムジェネレーション ザンスカール戦記
|
||||
name: SD Gundam Generation - Zanscare Senki
|
||||
region: SFT-0110-JPN
|
||||
revision: GUNDAM E
|
||||
board: PT-912
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x2000
|
||||
content: Save
|
||||
note: Linkable: SFT-0104-JPN, SFT-0105-JPN, SFT-0107-JPN, SFT-0108-JPN, SFT-0110-JPN, SFT-0111-JPN
|
||||
|
||||
game
|
||||
sha256: 2fec5f2bc7dee010af10569a3d2bc18715a79a126940800c3eade5abbd625e3f
|
||||
label: SDウルトラバトル セブン伝説
|
||||
name: SD Ultra Battle - Seven Densetsu
|
||||
region: SFT-0102-JPN
|
||||
revision: ULTRA SEVEN 1
|
||||
board: PT-911
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x800
|
||||
content: Save
|
||||
note: Linkable: SFT-0101-JPN, SFT-0102-JPN
|
||||
|
||||
game
|
||||
sha256: 2bb55214fb668ca603d7b944b14f105dfb10b987a8902d420fe4ae1cb69c1d4a
|
||||
label: SDウルトラバトル ウルトラマン伝説
|
||||
name: SD Ultra Battle - Ultraman Densetsu
|
||||
region: SFT-0101-JPN
|
||||
revision: ULTRA MAN 1
|
||||
board: PT-911
|
||||
memory
|
||||
type: ROM
|
||||
size: 0x80000
|
||||
content: Program
|
||||
memory
|
||||
type: RAM
|
||||
size: 0x800
|
||||
content: Save
|
||||
note: Linkable: SFT-0101-JPN, SFT-0102-JPN
|
||||
|
16101
bsnes/Database/Super Famicom.bml
Normal file
64
bsnes/GNUmakefile
Normal file
|
@ -0,0 +1,64 @@
|
|||
target := bsnes
|
||||
binary := application
|
||||
build := performance
|
||||
openmp := true
|
||||
local := true
|
||||
flags += -I. -I..
|
||||
|
||||
# in order for this to work, obj/lzma.o must be omitted or bsnes will hang on startup.
|
||||
# further, only the X-Video driver works reliably. OpenGL 3.2, OpenGL 2.0, and XShm crash bsnes.
|
||||
ifeq ($(profile),true)
|
||||
flags += -pg
|
||||
options += -pg
|
||||
endif
|
||||
|
||||
# binaries built with this flag are faster, but are not portable to multiple machines.
|
||||
ifeq ($(local),true)
|
||||
flags += -march=native
|
||||
endif
|
||||
|
||||
nall.path := ../nall
|
||||
include $(nall.path)/GNUmakefile
|
||||
|
||||
ifeq ($(platform),windows)
|
||||
ifeq ($(binary),application)
|
||||
options += -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
options += -Wl,-enable-auto-import
|
||||
options += -Wl,-enable-runtime-pseudo-reloc
|
||||
else ifeq ($(binary),library)
|
||||
options += -shared
|
||||
endif
|
||||
else ifeq ($(platform),macos)
|
||||
ifeq ($(binary),application)
|
||||
else ifeq ($(binary),library)
|
||||
flags += -fPIC
|
||||
options += -dynamiclib
|
||||
endif
|
||||
else ifneq ($(filter $(platform),linux bsd),)
|
||||
ifeq ($(binary),application)
|
||||
options += -Wl,-export-dynamic
|
||||
options += -lX11 -lXext
|
||||
else ifeq ($(binary),library)
|
||||
flags += -fPIC
|
||||
options += -shared
|
||||
endif
|
||||
endif
|
||||
|
||||
objects := libco emulator filter lzma
|
||||
|
||||
obj/libco.o: ../libco/libco.c
|
||||
obj/emulator.o: emulator/emulator.cpp
|
||||
obj/filter.o: filter/filter.cpp
|
||||
obj/lzma.o: lzma/lzma.cpp
|
||||
|
||||
include sfc/GNUmakefile
|
||||
include gb/GNUmakefile
|
||||
include processor/GNUmakefile
|
||||
|
||||
ui := target-$(target)
|
||||
include $(ui)/GNUmakefile
|
||||
-include obj/*.d
|
||||
|
||||
clean:
|
||||
$(call delete,obj/*)
|
||||
$(call delete,out/*)
|
127
bsnes/Locale/Japanese.bml
Normal file
|
@ -0,0 +1,127 @@
|
|||
locale
|
||||
language: 日本語
|
||||
|
||||
namespace: MessageDialog
|
||||
map
|
||||
input: Yes
|
||||
value: はい
|
||||
map
|
||||
input: No
|
||||
value: いいえ
|
||||
map
|
||||
input: Cancel
|
||||
value: キャンセル
|
||||
|
||||
namespace: BrowserDialog
|
||||
map
|
||||
input: Open
|
||||
value: 開く
|
||||
map
|
||||
input: Save
|
||||
value: 保存
|
||||
map
|
||||
input: Select
|
||||
value: 選択
|
||||
map
|
||||
input: Cancel
|
||||
value: キャンセル
|
||||
|
||||
namespace: Program
|
||||
map
|
||||
input: Unloaded
|
||||
value: アンロードされる
|
||||
map
|
||||
input: Paused
|
||||
value: ポーズ
|
||||
|
||||
namespace: Presentation
|
||||
map
|
||||
input: System
|
||||
value: システム
|
||||
map
|
||||
input: Load Game
|
||||
value: ゲームを読み込み
|
||||
map
|
||||
input: Load Recent Game
|
||||
value: 最新ゲームを読み込み
|
||||
map
|
||||
input: empty
|
||||
value: なし
|
||||
map
|
||||
input: Clear List
|
||||
value: 全部を消す
|
||||
map
|
||||
input: Reset System
|
||||
value: システムをリセット
|
||||
map
|
||||
input: Unload Game
|
||||
value: ゲームをアンロード
|
||||
map
|
||||
input: Controller Port 1
|
||||
value: コントローラポート1
|
||||
map
|
||||
input: Controller Port 2
|
||||
value: コントローラポート2
|
||||
map
|
||||
input: Expansion Port
|
||||
value: 拡張ポート
|
||||
map
|
||||
input: Gamepad
|
||||
value: ゲームパッド
|
||||
map
|
||||
input: Mouse
|
||||
value: マウス
|
||||
map
|
||||
input: Super Multitap
|
||||
value: スーパーマルチタップ
|
||||
map
|
||||
input: Super Scope
|
||||
value: スーパースコップ
|
||||
map
|
||||
input: Justifier
|
||||
value: 1挺のジャスティファイアー
|
||||
map
|
||||
input: Justifiers
|
||||
value: 2挺のジャスティファイアー
|
||||
map
|
||||
input: None
|
||||
value: なし
|
||||
map
|
||||
input: Satellaview
|
||||
value: サテラビュー
|
||||
map
|
||||
input: Quit
|
||||
value: 終了
|
||||
map
|
||||
input: Settings
|
||||
value: 設定
|
||||
map
|
||||
input: Tools
|
||||
value: ツール
|
||||
map
|
||||
input: Help
|
||||
value: ヘルプ
|
||||
map
|
||||
input: Documentation
|
||||
value: オンラインマニュアル
|
||||
map
|
||||
input: About
|
||||
value: 情報
|
||||
|
||||
namespace: AboutWindow
|
||||
map
|
||||
input: About {0}
|
||||
value: {0}について
|
||||
map
|
||||
input: Version
|
||||
value: バージョン
|
||||
map
|
||||
input: Author
|
||||
value: 作者
|
||||
map
|
||||
input: License
|
||||
value: ライセンス
|
||||
map
|
||||
input: Website
|
||||
value: 公式サイト
|
||||
|
53
bsnes/README.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
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
|
73
bsnes/emulator/audio/audio.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
namespace Emulator {
|
||||
|
||||
#include "stream.cpp"
|
||||
Audio audio;
|
||||
|
||||
Audio::~Audio() {
|
||||
reset(nullptr);
|
||||
}
|
||||
|
||||
auto Audio::reset(Interface* interface) -> void {
|
||||
_interface = interface;
|
||||
_streams.reset();
|
||||
_channels = 0;
|
||||
}
|
||||
|
||||
auto Audio::setFrequency(double frequency) -> void {
|
||||
_frequency = frequency;
|
||||
for(auto& stream : _streams) {
|
||||
stream->setFrequency(stream->inputFrequency, frequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto Audio::setVolume(double volume) -> void {
|
||||
_volume = volume;
|
||||
}
|
||||
|
||||
auto Audio::setBalance(double balance) -> void {
|
||||
_balance = balance;
|
||||
}
|
||||
|
||||
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
|
||||
_channels = max(_channels, channels);
|
||||
shared_pointer<Stream> stream = new Stream;
|
||||
stream->reset(channels, frequency, _frequency);
|
||||
_streams.append(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
auto Audio::process() -> void {
|
||||
while(_streams) {
|
||||
for(auto& stream : _streams) {
|
||||
if(!stream->pending()) return;
|
||||
}
|
||||
|
||||
double samples[_channels];
|
||||
for(auto& sample : samples) sample = 0.0;
|
||||
|
||||
for(auto& stream : _streams) {
|
||||
double buffer[_channels];
|
||||
uint length = stream->read(buffer), offset = 0;
|
||||
|
||||
for(auto& sample : samples) {
|
||||
sample += buffer[offset];
|
||||
if(++offset >= length) offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto c : range(_channels)) {
|
||||
samples[c] = max(-1.0, min(+1.0, samples[c] * _volume));
|
||||
}
|
||||
|
||||
if(_channels == 2) {
|
||||
if(_balance < 0.0) samples[1] *= 1.0 + _balance;
|
||||
if(_balance > 0.0) samples[0] *= 1.0 - _balance;
|
||||
}
|
||||
|
||||
platform->audioFrame(samples, _channels);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#undef double
|
94
bsnes/emulator/audio/audio.hpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/dsp/iir/dc-removal.hpp>
|
||||
#include <nall/dsp/iir/one-pole.hpp>
|
||||
#include <nall/dsp/iir/biquad.hpp>
|
||||
#include <nall/dsp/resampler/cubic.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Interface;
|
||||
struct Audio;
|
||||
struct Filter;
|
||||
struct Stream;
|
||||
|
||||
struct Audio {
|
||||
~Audio();
|
||||
auto reset(Interface* interface) -> void;
|
||||
|
||||
inline auto channels() const -> uint { return _channels; }
|
||||
inline auto frequency() const -> double { return _frequency; }
|
||||
inline auto volume() const -> double { return _volume; }
|
||||
inline auto balance() const -> double { return _balance; }
|
||||
|
||||
auto setFrequency(double frequency) -> void;
|
||||
auto setVolume(double volume) -> void;
|
||||
auto setBalance(double balance) -> void;
|
||||
|
||||
auto createStream(uint channels, double frequency) -> shared_pointer<Stream>;
|
||||
|
||||
private:
|
||||
auto process() -> void;
|
||||
|
||||
Interface* _interface = nullptr;
|
||||
vector<shared_pointer<Stream>> _streams;
|
||||
|
||||
uint _channels = 0;
|
||||
double _frequency = 48000.0;
|
||||
|
||||
double _volume = 1.0;
|
||||
double _balance = 0.0;
|
||||
|
||||
friend class Stream;
|
||||
};
|
||||
|
||||
struct Filter {
|
||||
enum class Mode : uint { DCRemoval, OnePole, Biquad } mode;
|
||||
enum class Type : uint { None, LowPass, HighPass } type;
|
||||
enum class Order : uint { None, First, Second } order;
|
||||
|
||||
DSP::IIR::DCRemoval dcRemoval;
|
||||
DSP::IIR::OnePole onePole;
|
||||
DSP::IIR::Biquad biquad;
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto frequency() const -> double;
|
||||
auto setFrequency(double inputFrequency, maybe<double> outputFrequency = nothing) -> void;
|
||||
|
||||
auto addDCRemovalFilter() -> void;
|
||||
auto addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void;
|
||||
auto addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void;
|
||||
|
||||
auto pending() const -> uint;
|
||||
auto read(double samples[]) -> uint;
|
||||
auto write(const double samples[]) -> void;
|
||||
|
||||
template<typename... P> auto sample(P&&... p) -> void {
|
||||
double samples[sizeof...(P)] = {forward<P>(p)...};
|
||||
write(samples);
|
||||
}
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
private:
|
||||
struct Channel {
|
||||
vector<Filter> filters;
|
||||
vector<DSP::IIR::Biquad> nyquist;
|
||||
DSP::Resampler::Cubic resampler;
|
||||
};
|
||||
vector<Channel> channels;
|
||||
double inputFrequency;
|
||||
double outputFrequency;
|
||||
|
||||
friend class Audio;
|
||||
};
|
||||
|
||||
extern Audio audio;
|
||||
|
||||
}
|
||||
|
||||
#undef double
|
125
bsnes/emulator/audio/stream.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
auto Stream::reset(uint channelCount, double inputFrequency, double outputFrequency) -> void {
|
||||
channels.reset();
|
||||
channels.resize(channelCount);
|
||||
|
||||
for(auto& channel : channels) {
|
||||
channel.filters.reset();
|
||||
}
|
||||
|
||||
setFrequency(inputFrequency, outputFrequency);
|
||||
}
|
||||
|
||||
auto Stream::reset() -> void {
|
||||
for(auto& channel : channels) {
|
||||
channel.resampler.reset(this->inputFrequency, this->outputFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::frequency() const -> double {
|
||||
return inputFrequency;
|
||||
}
|
||||
|
||||
auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void {
|
||||
this->inputFrequency = inputFrequency;
|
||||
if(outputFrequency) this->outputFrequency = outputFrequency();
|
||||
|
||||
for(auto& channel : channels) {
|
||||
channel.nyquist.reset();
|
||||
channel.resampler.reset(this->inputFrequency, this->outputFrequency);
|
||||
}
|
||||
|
||||
if(this->inputFrequency >= this->outputFrequency * 2) {
|
||||
//add a low-pass filter to prevent aliasing during resampling
|
||||
double cutoffFrequency = min(25000.0, this->outputFrequency / 2.0 - 2000.0);
|
||||
for(auto& channel : channels) {
|
||||
uint passes = 3;
|
||||
for(uint pass : range(passes)) {
|
||||
DSP::IIR::Biquad filter;
|
||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, this->inputFrequency, q);
|
||||
channel.nyquist.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addDCRemovalFilter() -> void {
|
||||
return; //todo: test to ensure this is desirable before enabling
|
||||
for(auto& channel : channels) {
|
||||
Filter filter{Filter::Mode::DCRemoval, Filter::Type::None, Filter::Order::None};
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
|
||||
for(auto& channel : channels) {
|
||||
for(uint pass : range(passes)) {
|
||||
if(order == Filter::Order::First) {
|
||||
Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First};
|
||||
filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, inputFrequency);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
if(order == Filter::Order::Second) {
|
||||
Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second};
|
||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
|
||||
for(auto& channel : channels) {
|
||||
for(uint pass : range(passes)) {
|
||||
if(order == Filter::Order::First) {
|
||||
Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First};
|
||||
filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, inputFrequency);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
if(order == Filter::Order::Second) {
|
||||
Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second};
|
||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::pending() const -> uint {
|
||||
if(!channels) return 0;
|
||||
return channels[0].resampler.pending();
|
||||
}
|
||||
|
||||
auto Stream::read(double samples[]) -> uint {
|
||||
for(uint c : range(channels.size())) samples[c] = channels[c].resampler.read();
|
||||
return channels.size();
|
||||
}
|
||||
|
||||
auto Stream::write(const double samples[]) -> void {
|
||||
for(auto c : range(channels.size())) {
|
||||
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
|
||||
for(auto& filter : channels[c].filters) {
|
||||
switch(filter.mode) {
|
||||
case Filter::Mode::DCRemoval: sample = filter.dcRemoval.process(sample); break;
|
||||
case Filter::Mode::OnePole: sample = filter.onePole.process(sample); break;
|
||||
case Filter::Mode::Biquad: sample = filter.biquad.process(sample); break;
|
||||
}
|
||||
}
|
||||
for(auto& filter : channels[c].nyquist) {
|
||||
sample = filter.process(sample);
|
||||
}
|
||||
channels[c].resampler.write(sample);
|
||||
}
|
||||
|
||||
audio.process();
|
||||
}
|
||||
|
||||
auto Stream::serialize(serializer& s) -> void {
|
||||
for(auto& channel : channels) {
|
||||
channel.resampler.serialize(s);
|
||||
}
|
||||
s.real(inputFrequency);
|
||||
s.real(outputFrequency);
|
||||
}
|
57
bsnes/emulator/cheat.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Cheat {
|
||||
struct Code {
|
||||
auto operator==(const Code& code) const -> bool {
|
||||
if(address != code.address) return false;
|
||||
if(data != code.data) return false;
|
||||
if((bool)compare != (bool)code.compare) return false;
|
||||
if(compare && code.compare && compare() != code.compare()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint address;
|
||||
uint data;
|
||||
maybe<uint> compare;
|
||||
bool enable;
|
||||
uint restore;
|
||||
};
|
||||
|
||||
explicit operator bool() const {
|
||||
return codes.size() > 0;
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
codes.reset();
|
||||
}
|
||||
|
||||
auto append(uint address, uint data, maybe<uint> compare = {}) -> void {
|
||||
codes.append({address, data, compare});
|
||||
}
|
||||
|
||||
auto assign(const vector<string>& list) -> void {
|
||||
reset();
|
||||
for(auto& entry : list) {
|
||||
for(auto code : entry.split("+")) {
|
||||
auto part = code.transform("=?", "//").split("/");
|
||||
if(part.size() == 2) append(part[0].hex(), part[1].hex());
|
||||
if(part.size() == 3) append(part[0].hex(), part[2].hex(), part[1].hex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto find(uint address, uint compare) -> maybe<uint> {
|
||||
for(auto& code : codes) {
|
||||
if(code.address == address && (!code.compare || code.compare() == compare)) {
|
||||
return code.data;
|
||||
}
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
vector<Code> codes;
|
||||
};
|
||||
|
||||
}
|
8
bsnes/emulator/emulator.cpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
#include <emulator/emulator.hpp>
|
||||
#include <emulator/audio/audio.cpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
Platform* platform = nullptr;
|
||||
|
||||
}
|
58
bsnes/emulator/emulator.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/adaptive-array.hpp>
|
||||
#include <nall/any.hpp>
|
||||
#include <nall/chrono.hpp>
|
||||
#include <nall/dl.hpp>
|
||||
#include <nall/endian.hpp>
|
||||
#include <nall/image.hpp>
|
||||
#include <nall/literals.hpp>
|
||||
#include <nall/random.hpp>
|
||||
#include <nall/serializer.hpp>
|
||||
#include <nall/shared-pointer.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
#include <nall/unique-pointer.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
#include <nall/vfs.hpp>
|
||||
#include <nall/hash/crc32.hpp>
|
||||
#include <nall/hash/sha256.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <emulator/types.hpp>
|
||||
#include <emulator/memory/readable.hpp>
|
||||
#include <emulator/memory/writable.hpp>
|
||||
#include <emulator/audio/audio.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "bsnes";
|
||||
static const string Version = "115";
|
||||
static const string Copyright = "bsnes team";
|
||||
static const string License = "GPLv3 or later";
|
||||
static const string Website = "https://bsnes.dev";
|
||||
|
||||
//incremented only when serialization format changes
|
||||
static const string SerializerVersion = "115";
|
||||
|
||||
namespace Constants {
|
||||
namespace Colorburst {
|
||||
static constexpr double NTSC = 315.0 / 88.0 * 1'000'000.0;
|
||||
static constexpr double PAL = 283.75 * 15'625.0 + 25.0;
|
||||
}
|
||||
}
|
||||
|
||||
//nall/vfs shorthand constants for open(), load()
|
||||
namespace File {
|
||||
static const auto Read = vfs::file::mode::read;
|
||||
static const auto Write = vfs::file::mode::write;
|
||||
static const auto Optional = false;
|
||||
static const auto Required = true;
|
||||
};
|
||||
}
|
||||
|
||||
#include "platform.hpp"
|
||||
#include "interface.hpp"
|
||||
#include "game.hpp"
|
112
bsnes/emulator/game.hpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Game {
|
||||
struct Memory;
|
||||
struct Oscillator;
|
||||
|
||||
inline auto load(string_view) -> void;
|
||||
inline auto memory(Markup::Node) -> maybe<Memory>;
|
||||
inline auto oscillator(natural = 0) -> maybe<Oscillator>;
|
||||
|
||||
struct Memory {
|
||||
Memory() = default;
|
||||
inline Memory(Markup::Node);
|
||||
explicit operator bool() const { return (bool)type; }
|
||||
inline auto name() const -> string;
|
||||
|
||||
string type;
|
||||
natural size;
|
||||
string content;
|
||||
string manufacturer;
|
||||
string architecture;
|
||||
string identifier;
|
||||
boolean nonVolatile;
|
||||
};
|
||||
|
||||
struct Oscillator {
|
||||
Oscillator() = default;
|
||||
inline Oscillator(Markup::Node);
|
||||
explicit operator bool() const { return frequency; }
|
||||
|
||||
natural frequency;
|
||||
};
|
||||
|
||||
Markup::Node document;
|
||||
string sha256;
|
||||
string label;
|
||||
string name;
|
||||
string title;
|
||||
string region;
|
||||
string revision;
|
||||
string board;
|
||||
vector<Memory> memoryList;
|
||||
vector<Oscillator> oscillatorList;
|
||||
};
|
||||
|
||||
auto Game::load(string_view text) -> void {
|
||||
document = BML::unserialize(text);
|
||||
|
||||
sha256 = document["game/sha256"].text();
|
||||
label = document["game/label"].text();
|
||||
name = document["game/name"].text();
|
||||
title = document["game/title"].text();
|
||||
region = document["game/region"].text();
|
||||
revision = document["game/revision"].text();
|
||||
board = document["game/board"].text();
|
||||
|
||||
for(auto node : document.find("game/board/memory")) {
|
||||
memoryList.append(Memory{node});
|
||||
}
|
||||
|
||||
for(auto node : document.find("game/board/oscillator")) {
|
||||
oscillatorList.append(Oscillator{node});
|
||||
}
|
||||
}
|
||||
|
||||
auto Game::memory(Markup::Node node) -> maybe<Memory> {
|
||||
if(!node) return nothing;
|
||||
for(auto& memory : memoryList) {
|
||||
auto type = node["type"].text();
|
||||
auto size = node["size"].natural();
|
||||
auto content = node["content"].text();
|
||||
auto manufacturer = node["manufacturer"].text();
|
||||
auto architecture = node["architecture"].text();
|
||||
auto identifier = node["identifier"].text();
|
||||
if(type && type != memory.type) continue;
|
||||
if(size && size != memory.size) continue;
|
||||
if(content && content != memory.content) continue;
|
||||
if(manufacturer && manufacturer != memory.manufacturer) continue;
|
||||
if(architecture && architecture != memory.architecture) continue;
|
||||
if(identifier && identifier != memory.identifier) continue;
|
||||
return memory;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto Game::oscillator(natural index) -> maybe<Oscillator> {
|
||||
if(index < oscillatorList.size()) return oscillatorList[index];
|
||||
return nothing;
|
||||
}
|
||||
|
||||
Game::Memory::Memory(Markup::Node node) {
|
||||
type = node["type"].text();
|
||||
size = node["size"].natural();
|
||||
content = node["content"].text();
|
||||
manufacturer = node["manufacturer"].text();
|
||||
architecture = node["architecture"].text();
|
||||
identifier = node["identifier"].text();
|
||||
nonVolatile = !(bool)node["volatile"];
|
||||
}
|
||||
|
||||
auto Game::Memory::name() const -> string {
|
||||
if(architecture) return string{architecture, ".", content, ".", type}.downcase();
|
||||
return string{content, ".", type}.downcase();
|
||||
}
|
||||
|
||||
Game::Oscillator::Oscillator(Markup::Node node) {
|
||||
frequency = node["frequency"].natural();
|
||||
}
|
||||
|
||||
}
|
109
bsnes/emulator/interface.hpp
Normal file
|
@ -0,0 +1,109 @@
|
|||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Interface {
|
||||
struct Information {
|
||||
string manufacturer;
|
||||
string name;
|
||||
string extension;
|
||||
bool resettable = false;
|
||||
};
|
||||
|
||||
struct Display {
|
||||
struct Type { enum : uint {
|
||||
CRT,
|
||||
LCD,
|
||||
};};
|
||||
uint id = 0;
|
||||
string name;
|
||||
uint type = 0;
|
||||
uint colors = 0;
|
||||
uint width = 0;
|
||||
uint height = 0;
|
||||
uint internalWidth = 0;
|
||||
uint internalHeight = 0;
|
||||
double aspectCorrection = 0;
|
||||
};
|
||||
|
||||
struct Port {
|
||||
uint id;
|
||||
string name;
|
||||
};
|
||||
|
||||
struct Device {
|
||||
uint id;
|
||||
string name;
|
||||
};
|
||||
|
||||
struct Input {
|
||||
struct Type { enum : uint {
|
||||
Hat,
|
||||
Button,
|
||||
Trigger,
|
||||
Control,
|
||||
Axis,
|
||||
Rumble,
|
||||
};};
|
||||
|
||||
uint type;
|
||||
string name;
|
||||
};
|
||||
|
||||
//information
|
||||
virtual auto information() -> Information { return {}; }
|
||||
|
||||
virtual auto display() -> Display { return {}; }
|
||||
virtual auto color(uint32 color) -> uint64 { return 0; }
|
||||
|
||||
//game interface
|
||||
virtual auto loaded() -> bool { return false; }
|
||||
virtual auto hashes() -> vector<string> { return {}; }
|
||||
virtual auto manifests() -> vector<string> { return {}; }
|
||||
virtual auto titles() -> vector<string> { return {}; }
|
||||
virtual auto title() -> string { return {}; }
|
||||
virtual auto load() -> bool { return false; }
|
||||
virtual auto save() -> void {}
|
||||
virtual auto unload() -> void {}
|
||||
|
||||
//system interface
|
||||
virtual auto ports() -> vector<Port> { return {}; }
|
||||
virtual auto devices(uint port) -> vector<Device> { return {}; }
|
||||
virtual auto inputs(uint device) -> vector<Input> { return {}; }
|
||||
virtual auto connected(uint port) -> uint { return 0; }
|
||||
virtual auto connect(uint port, uint device) -> void {}
|
||||
virtual auto power() -> void {}
|
||||
virtual auto reset() -> void {}
|
||||
virtual auto run() -> void {}
|
||||
|
||||
//time functions
|
||||
virtual auto rtc() -> bool { return false; }
|
||||
virtual auto synchronize(uint64 timestamp = 0) -> void {}
|
||||
|
||||
//state functions
|
||||
virtual auto serialize(bool synchronize = true) -> serializer { return {}; }
|
||||
virtual auto unserialize(serializer&) -> bool { return false; }
|
||||
|
||||
//cheat functions
|
||||
virtual auto read(uint24 address) -> uint8 { return 0; }
|
||||
virtual auto cheats(const vector<string>& = {}) -> void {}
|
||||
|
||||
//configuration
|
||||
virtual auto configuration() -> string { return {}; }
|
||||
virtual auto configuration(string name) -> string { return {}; }
|
||||
virtual auto configure(string configuration = "") -> bool { return false; }
|
||||
virtual auto configure(string name, string value) -> bool { return false; }
|
||||
|
||||
//settings
|
||||
virtual auto cap(const string& name) -> bool { return false; }
|
||||
virtual auto get(const string& name) -> any { return {}; }
|
||||
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
||||
|
||||
virtual auto frameSkip() -> uint { return 0; }
|
||||
virtual auto setFrameSkip(uint frameSkip) -> void {}
|
||||
|
||||
virtual auto runAhead() -> bool { return false; }
|
||||
virtual auto setRunAhead(bool runAhead) -> void {}
|
||||
};
|
||||
|
||||
}
|
30
bsnes/emulator/memory/memory.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
namespace Emulator::Memory {
|
||||
|
||||
inline auto mirror(uint address, uint size) -> uint {
|
||||
if(size == 0) return 0;
|
||||
uint base = 0;
|
||||
uint mask = 1 << 31;
|
||||
while(address >= size) {
|
||||
while(!(address & mask)) mask >>= 1;
|
||||
address -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
return base + address;
|
||||
}
|
||||
|
||||
inline auto reduce(uint address, uint mask) -> uint {
|
||||
while(mask) {
|
||||
uint bits = (mask & -mask) - 1;
|
||||
address = address >> 1 & ~bits | address & bits;
|
||||
mask = (mask & mask - 1) >> 1;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
}
|
63
bsnes/emulator/memory/readable.hpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <emulator/memory/memory.hpp>
|
||||
|
||||
namespace Emulator::Memory {
|
||||
|
||||
template<typename T>
|
||||
struct Readable {
|
||||
~Readable() { reset(); }
|
||||
|
||||
inline auto reset() -> void {
|
||||
delete[] self.data;
|
||||
self.data = nullptr;
|
||||
self.size = 0;
|
||||
self.mask = 0;
|
||||
}
|
||||
|
||||
inline auto allocate(uint size, T fill = ~0ull) -> void {
|
||||
if(!size) return reset();
|
||||
delete[] self.data;
|
||||
self.size = size;
|
||||
self.mask = bit::round(self.size) - 1;
|
||||
self.data = new T[self.mask + 1];
|
||||
memory::fill<T>(self.data, self.mask + 1, fill);
|
||||
}
|
||||
|
||||
inline auto load(shared_pointer<vfs::file> fp) -> void {
|
||||
fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
|
||||
for(uint address = self.size; address <= self.mask; address++) {
|
||||
self.data[address] = self.data[mirror(address, self.size)];
|
||||
}
|
||||
}
|
||||
|
||||
inline auto save(shared_pointer<vfs::file> fp) -> void {
|
||||
fp->write(self.data, self.size * sizeof(T));
|
||||
}
|
||||
|
||||
explicit operator bool() const { return (bool)self.data; }
|
||||
inline auto data() const -> const T* { return self.data; }
|
||||
inline auto size() const -> uint { return self.size; }
|
||||
inline auto mask() const -> uint { return self.mask; }
|
||||
|
||||
inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
|
||||
inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
|
||||
inline auto write(uint address, T data) const -> void {}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
const uint size = self.size;
|
||||
s.integer(self.size);
|
||||
s.integer(self.mask);
|
||||
if(self.size != size) allocate(self.size);
|
||||
s.array(self.data, self.size);
|
||||
}
|
||||
|
||||
private:
|
||||
struct {
|
||||
T* data = nullptr;
|
||||
uint size = 0;
|
||||
uint mask = 0;
|
||||
} self;
|
||||
};
|
||||
|
||||
}
|
65
bsnes/emulator/memory/writable.hpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#include <emulator/memory/memory.hpp>
|
||||
|
||||
namespace Emulator::Memory {
|
||||
|
||||
template<typename T>
|
||||
struct Writable {
|
||||
~Writable() { reset(); }
|
||||
|
||||
inline auto reset() -> void {
|
||||
delete[] self.data;
|
||||
self.data = nullptr;
|
||||
self.size = 0;
|
||||
self.mask = 0;
|
||||
}
|
||||
|
||||
inline auto allocate(uint size, T fill = ~0ull) -> void {
|
||||
if(!size) return reset();
|
||||
delete[] self.data;
|
||||
self.size = size;
|
||||
self.mask = bit::round(self.size) - 1;
|
||||
self.data = new T[self.mask + 1];
|
||||
memory::fill<T>(self.data, self.mask + 1, fill);
|
||||
}
|
||||
|
||||
inline auto load(shared_pointer<vfs::file> fp) -> void {
|
||||
fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
|
||||
for(uint address = self.size; address <= self.mask; address++) {
|
||||
self.data[address] = self.data[mirror(address, self.size)];
|
||||
}
|
||||
}
|
||||
|
||||
inline auto save(shared_pointer<vfs::file> fp) -> void {
|
||||
fp->write(self.data, self.size * sizeof(T));
|
||||
}
|
||||
|
||||
explicit operator bool() const { return (bool)self.data; }
|
||||
inline auto data() -> T* { return self.data; }
|
||||
inline auto data() const -> const T* { return self.data; }
|
||||
inline auto size() const -> uint { return self.size; }
|
||||
inline auto mask() const -> uint { return self.mask; }
|
||||
|
||||
inline auto operator[](uint address) -> T& { return self.data[address & self.mask]; }
|
||||
inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
|
||||
inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
|
||||
inline auto write(uint address, T data) -> void { self.data[address & self.mask] = data; }
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
const uint size = self.size;
|
||||
s.integer(self.size);
|
||||
s.integer(self.mask);
|
||||
if(self.size != size) allocate(self.size);
|
||||
s.array(self.data, self.size);
|
||||
}
|
||||
|
||||
private:
|
||||
struct {
|
||||
T* data = nullptr;
|
||||
uint size = 0;
|
||||
uint mask = 0;
|
||||
} self;
|
||||
};
|
||||
|
||||
}
|
29
bsnes/emulator/platform.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Platform {
|
||||
struct Load {
|
||||
Load() = default;
|
||||
Load(uint pathID, string option = "") : valid(true), pathID(pathID), option(option) {}
|
||||
explicit operator bool() const { return valid; }
|
||||
|
||||
bool valid = false;
|
||||
uint pathID = 0;
|
||||
string option;
|
||||
};
|
||||
|
||||
virtual auto path(uint id) -> string { 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 videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void {}
|
||||
virtual auto audioFrame(const double* samples, uint channels) -> void {}
|
||||
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 dipSettings(Markup::Node node) -> uint { return 0; }
|
||||
virtual auto notify(string text) -> void {}
|
||||
};
|
||||
|
||||
extern Platform* platform;
|
||||
|
||||
}
|
96
bsnes/emulator/random.hpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Random {
|
||||
enum class Entropy : uint { None, Low, High };
|
||||
|
||||
auto operator()() -> uint64 {
|
||||
return random();
|
||||
}
|
||||
|
||||
auto entropy(Entropy entropy) -> void {
|
||||
_entropy = entropy;
|
||||
seed();
|
||||
}
|
||||
|
||||
auto seed(maybe<uint32> seed = nothing, maybe<uint32> sequence = nothing) -> void {
|
||||
if(!seed) seed = (uint32)clock();
|
||||
if(!sequence) sequence = 0;
|
||||
|
||||
_state = 0;
|
||||
_increment = sequence() << 1 | 1;
|
||||
step();
|
||||
_state += seed();
|
||||
step();
|
||||
}
|
||||
|
||||
auto random() -> uint64 {
|
||||
if(_entropy == Entropy::None) return 0;
|
||||
return (uint64)step() << 32 | (uint64)step() << 0;
|
||||
}
|
||||
|
||||
auto bias(uint64 bias) -> uint64 {
|
||||
if(_entropy == Entropy::None) return bias;
|
||||
return random();
|
||||
}
|
||||
|
||||
auto bound(uint64 bound) -> uint64 {
|
||||
uint64 threshold = -bound % bound;
|
||||
while(true) {
|
||||
uint64 result = random();
|
||||
if(result >= threshold) return result % bound;
|
||||
}
|
||||
}
|
||||
|
||||
auto array(uint8* data, uint32 size) -> void {
|
||||
if(_entropy == Entropy::None) {
|
||||
memory::fill(data, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if(_entropy == Entropy::High) {
|
||||
for(uint32 address : range(size)) {
|
||||
data[address] = random();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Entropy::Low
|
||||
uint lobit = random() & 3;
|
||||
uint hibit = (lobit + 8 + (random() & 3)) & 15;
|
||||
uint lovalue = random() & 255;
|
||||
uint hivalue = random() & 255;
|
||||
if((random() & 3) == 0) lovalue = 0;
|
||||
if((random() & 1) == 0) hivalue = ~lovalue;
|
||||
|
||||
for(uint32 address : range(size)) {
|
||||
uint8 value = (address & 1ull << lobit) ? lovalue : hivalue;
|
||||
if((address & 1ull << hibit)) value = ~value;
|
||||
if((random() & 511) == 0) value ^= 1 << (random() & 7);
|
||||
if((random() & 2047) == 0) value ^= 1 << (random() & 7);
|
||||
data[address] = value;
|
||||
}
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer((uint&)_entropy);
|
||||
s.integer(_state);
|
||||
s.integer(_increment);
|
||||
}
|
||||
|
||||
private:
|
||||
auto step() -> uint32 {
|
||||
uint64 state = _state;
|
||||
_state = state * 6364136223846793005ull + _increment;
|
||||
uint32 xorshift = (state >> 18 ^ state) >> 27;
|
||||
uint32 rotate = state >> 59;
|
||||
return xorshift >> rotate | xorshift << (-rotate & 31);
|
||||
}
|
||||
|
||||
Entropy _entropy = Entropy::High;
|
||||
uint64 _state;
|
||||
uint64 _increment;
|
||||
};
|
||||
|
||||
}
|
72
bsnes/emulator/types.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
using int1 = nall::Integer< 1>;
|
||||
using int2 = nall::Integer< 2>;
|
||||
using int3 = nall::Integer< 3>;
|
||||
using int4 = nall::Integer< 4>;
|
||||
using int5 = nall::Integer< 5>;
|
||||
using int6 = nall::Integer< 6>;
|
||||
using int7 = nall::Integer< 7>;
|
||||
using int8 = int8_t;
|
||||
using int9 = nall::Integer< 9>;
|
||||
using int10 = nall::Integer<10>;
|
||||
using int11 = nall::Integer<11>;
|
||||
using int12 = nall::Integer<12>;
|
||||
using int13 = nall::Integer<13>;
|
||||
using int14 = nall::Integer<14>;
|
||||
using int15 = nall::Integer<15>;
|
||||
using int16 = int16_t;
|
||||
using int17 = nall::Integer<17>;
|
||||
using int18 = nall::Integer<18>;
|
||||
using int19 = nall::Integer<19>;
|
||||
using int20 = nall::Integer<20>;
|
||||
using int21 = nall::Integer<21>;
|
||||
using int22 = nall::Integer<22>;
|
||||
using int23 = nall::Integer<23>;
|
||||
using int24 = nall::Integer<24>;
|
||||
using int25 = nall::Integer<25>;
|
||||
using int26 = nall::Integer<26>;
|
||||
using int27 = nall::Integer<27>;
|
||||
using int28 = nall::Integer<28>;
|
||||
using int29 = nall::Integer<29>;
|
||||
using int30 = nall::Integer<30>;
|
||||
using int31 = nall::Integer<31>;
|
||||
using int32 = int32_t;
|
||||
using int48 = nall::Integer<48>; //Cx4
|
||||
using int64 = int64_t;
|
||||
|
||||
using uint1 = nall::Natural< 1>;
|
||||
using uint2 = nall::Natural< 2>;
|
||||
using uint3 = nall::Natural< 3>;
|
||||
using uint4 = nall::Natural< 4>;
|
||||
using uint5 = nall::Natural< 5>;
|
||||
using uint6 = nall::Natural< 6>;
|
||||
using uint7 = nall::Natural< 7>;
|
||||
using uint8 = uint8_t;
|
||||
using uint9 = nall::Natural< 9>;
|
||||
using uint10 = nall::Natural<10>;
|
||||
using uint11 = nall::Natural<11>;
|
||||
using uint12 = nall::Natural<12>;
|
||||
using uint13 = nall::Natural<13>;
|
||||
using uint14 = nall::Natural<14>;
|
||||
using uint15 = nall::Natural<15>;
|
||||
using uint16 = uint16_t;
|
||||
using uint17 = nall::Natural<17>;
|
||||
using uint18 = nall::Natural<18>;
|
||||
using uint19 = nall::Natural<19>;
|
||||
using uint20 = nall::Natural<20>;
|
||||
using uint21 = nall::Natural<21>;
|
||||
using uint22 = nall::Natural<22>;
|
||||
using uint23 = nall::Natural<23>;
|
||||
using uint24 = nall::Natural<24>;
|
||||
using uint25 = nall::Natural<25>;
|
||||
using uint26 = nall::Natural<26>;
|
||||
using uint27 = nall::Natural<27>;
|
||||
using uint28 = nall::Natural<28>;
|
||||
using uint29 = nall::Natural<29>;
|
||||
using uint30 = nall::Natural<30>;
|
||||
using uint31 = nall::Natural<31>;
|
||||
using uint32 = uint32_t;
|
||||
using uint40 = nall::Natural<40>; //SA1
|
||||
using uint48 = nall::Natural<48>; //Cx4
|
||||
using uint64 = uint64_t;
|
25
bsnes/filter/2xsai.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
namespace Filter::_2xSaI {
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width *= 2;
|
||||
height *= 2;
|
||||
}
|
||||
|
||||
uint32_t temp[512 * 480];
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
const uint16_t *line_in = (const uint16_t*)(((const uint8_t*)input) + pitch * y);
|
||||
uint32_t *line_out = temp + y * width;
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
line_out[x] = colortable[line_in[x]];
|
||||
}
|
||||
}
|
||||
|
||||
_2xSaI32((unsigned char*)temp, width * sizeof(uint32_t), 0, (unsigned char*)output, outpitch, width, height);
|
||||
}
|
||||
|
||||
}
|
25
bsnes/filter/filter.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include <emulator/emulator.hpp>
|
||||
|
||||
#undef register
|
||||
#define register
|
||||
#include "sai/sai.cpp"
|
||||
|
||||
uint32_t* colortable;
|
||||
#include "snes_ntsc/snes_ntsc.h"
|
||||
#include "snes_ntsc/snes_ntsc.c"
|
||||
|
||||
#include "none.cpp"
|
||||
#include "scanlines-light.cpp"
|
||||
#include "scanlines-dark.cpp"
|
||||
#include "scanlines-black.cpp"
|
||||
#include "pixellate2x.cpp"
|
||||
#include "scale2x.cpp"
|
||||
#include "2xsai.cpp"
|
||||
#include "super-2xsai.cpp"
|
||||
#include "super-eagle.cpp"
|
||||
#include "lq2x.cpp"
|
||||
#include "hq2x.cpp"
|
||||
#include "ntsc-rf.cpp"
|
||||
#include "ntsc-composite.cpp"
|
||||
#include "ntsc-svideo.cpp"
|
||||
#include "ntsc-rgb.cpp"
|
129
bsnes/filter/filter.hpp
Normal file
|
@ -0,0 +1,129 @@
|
|||
#pragma once
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Filter {
|
||||
using Size = auto (*)(uint& width, uint& height) -> void;
|
||||
using Render = auto (*)(uint32_t* palette, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::None {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::ScanlinesLight {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::ScanlinesDark {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::ScanlinesBlack {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::Pixellate2x {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::Scale2x {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::_2xSaI {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::Super2xSaI {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::SuperEagle {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::LQ2x {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::HQ2x {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::NTSC_RF {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::NTSC_Composite {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::NTSC_SVideo {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
||||
|
||||
namespace Filter::NTSC_RGB {
|
||||
auto size(uint& width, uint& height) -> void;
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void;
|
||||
}
|
200
bsnes/filter/hq2x.cpp
Normal file
|
@ -0,0 +1,200 @@
|
|||
namespace Filter::HQ2x {
|
||||
|
||||
enum {
|
||||
diff_offset = (0x440 << 21) + (0x207 << 11) + 0x407,
|
||||
diff_mask = (0x380 << 21) + (0x1f0 << 11) + 0x3f0,
|
||||
};
|
||||
|
||||
uint32_t *yuvTable;
|
||||
uint8_t rotate[256];
|
||||
|
||||
const uint8_t hqTable[256] = {
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13,
|
||||
4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 12, 12, 5, 3, 1, 12,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14,
|
||||
4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 16, 12, 5, 3, 1, 14,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 12, 12, 5, 19, 16, 12,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 1, 12, 5, 19, 1, 14,
|
||||
4, 4, 6, 2, 4, 4, 6, 18, 5, 3, 16, 12, 5, 19, 1, 14,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 13, 5, 3, 1, 14,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 13,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 12,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 14,
|
||||
4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 1, 12, 5, 3, 1, 14,
|
||||
};
|
||||
|
||||
static void initialize() {
|
||||
static bool initialized = false;
|
||||
if(initialized == true) return;
|
||||
initialized = true;
|
||||
|
||||
yuvTable = new uint32_t[32768];
|
||||
|
||||
for(unsigned i = 0; i < 32768; i++) {
|
||||
uint8_t R = (i >> 0) & 31;
|
||||
uint8_t G = (i >> 5) & 31;
|
||||
uint8_t B = (i >> 10) & 31;
|
||||
|
||||
//bgr555->bgr888
|
||||
double r = (R << 3) | (R >> 2);
|
||||
double g = (G << 3) | (G >> 2);
|
||||
double b = (B << 3) | (B >> 2);
|
||||
|
||||
//bgr888->yuv
|
||||
double y = (r + g + b) * (0.25f * (63.5f / 48.0f));
|
||||
double u = ((r - b) * 0.25f + 128.0f) * (7.5f / 7.0f);
|
||||
double v = ((g * 2.0f - r - b) * 0.125f + 128.0f) * (7.5f / 6.0f);
|
||||
|
||||
yuvTable[i] = ((unsigned)y << 21) + ((unsigned)u << 11) + ((unsigned)v);
|
||||
}
|
||||
|
||||
//counter-clockwise rotation table; one revolution:
|
||||
//123 369 12346789
|
||||
//4.6 -> 2.8 =
|
||||
//789 147 36928147
|
||||
for(unsigned n = 0; n < 256; n++) {
|
||||
rotate[n] = ((n >> 2) & 0x11) | ((n << 2) & 0x88)
|
||||
| ((n & 0x01) << 5) | ((n & 0x08) << 3)
|
||||
| ((n & 0x10) >> 3) | ((n & 0x80) >> 5);
|
||||
}
|
||||
}
|
||||
|
||||
static void terminate() {
|
||||
delete[] yuvTable;
|
||||
}
|
||||
|
||||
static bool same(uint16_t x, uint16_t y) {
|
||||
return !((yuvTable[x] - yuvTable[y] + diff_offset) & diff_mask);
|
||||
}
|
||||
|
||||
static bool diff(uint32_t x, uint16_t y) {
|
||||
return ((x - yuvTable[y]) & diff_mask);
|
||||
}
|
||||
|
||||
static void grow(uint32_t &n) { n |= n << 16; n &= 0x03e07c1f; }
|
||||
static uint16_t pack(uint32_t n) { n &= 0x03e07c1f; return n | (n >> 16); }
|
||||
|
||||
static uint16_t blend1(uint32_t A, uint32_t B) {
|
||||
grow(A); grow(B);
|
||||
return pack((A * 3 + B) >> 2);
|
||||
}
|
||||
|
||||
static uint16_t blend2(uint32_t A, uint32_t B, uint32_t C) {
|
||||
grow(A); grow(B); grow(C);
|
||||
return pack((A * 2 + B + C) >> 2);
|
||||
}
|
||||
|
||||
static uint16_t blend3(uint32_t A, uint32_t B, uint32_t C) {
|
||||
grow(A); grow(B); grow(C);
|
||||
return pack((A * 5 + B * 2 + C) >> 3);
|
||||
}
|
||||
|
||||
static uint16_t blend4(uint32_t A, uint32_t B, uint32_t C) {
|
||||
grow(A); grow(B); grow(C);
|
||||
return pack((A * 6 + B + C) >> 3);
|
||||
}
|
||||
|
||||
static uint16_t blend5(uint32_t A, uint32_t B, uint32_t C) {
|
||||
grow(A); grow(B); grow(C);
|
||||
return pack((A * 2 + (B + C) * 3) >> 3);
|
||||
}
|
||||
|
||||
static uint16_t blend6(uint32_t A, uint32_t B, uint32_t C) {
|
||||
grow(A); grow(B); grow(C);
|
||||
return pack((A * 14 + B + C) >> 4);
|
||||
}
|
||||
|
||||
static uint16_t blend(unsigned rule, uint16_t E, uint16_t A, uint16_t B, uint16_t D, uint16_t F, uint16_t H) {
|
||||
switch(rule) { default:
|
||||
case 0: return E;
|
||||
case 1: return blend1(E, A);
|
||||
case 2: return blend1(E, D);
|
||||
case 3: return blend1(E, B);
|
||||
case 4: return blend2(E, D, B);
|
||||
case 5: return blend2(E, A, B);
|
||||
case 6: return blend2(E, A, D);
|
||||
case 7: return blend3(E, B, D);
|
||||
case 8: return blend3(E, D, B);
|
||||
case 9: return blend4(E, D, B);
|
||||
case 10: return blend5(E, D, B);
|
||||
case 11: return blend6(E, D, B);
|
||||
case 12: return same(B, D) ? blend2(E, D, B) : E;
|
||||
case 13: return same(B, D) ? blend5(E, D, B) : E;
|
||||
case 14: return same(B, D) ? blend6(E, D, B) : E;
|
||||
case 15: return same(B, D) ? blend2(E, D, B) : blend1(E, A);
|
||||
case 16: return same(B, D) ? blend4(E, D, B) : blend1(E, A);
|
||||
case 17: return same(B, D) ? blend5(E, D, B) : blend1(E, A);
|
||||
case 18: return same(B, F) ? blend3(E, B, D) : blend1(E, D);
|
||||
case 19: return same(D, H) ? blend3(E, D, B) : blend1(E, B);
|
||||
}
|
||||
}
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width *= 2;
|
||||
height *= 2;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
initialize();
|
||||
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
for(uint y = 0; y < height; y++) {
|
||||
const uint16_t* in = input + y * pitch;
|
||||
uint32_t* out0 = output + y * outpitch * 2;
|
||||
uint32_t* out1 = output + y * outpitch * 2 + outpitch;
|
||||
|
||||
int prevline = (y == 0 ? 0 : pitch);
|
||||
int nextline = (y == height - 1 ? 0 : pitch);
|
||||
|
||||
in++;
|
||||
*out0++ = 0; *out0++ = 0;
|
||||
*out1++ = 0; *out1++ = 0;
|
||||
|
||||
for(unsigned x = 1; x < width - 1; x++) {
|
||||
uint16_t A = *(in - prevline - 1);
|
||||
uint16_t B = *(in - prevline + 0);
|
||||
uint16_t C = *(in - prevline + 1);
|
||||
uint16_t D = *(in - 1);
|
||||
uint16_t E = *(in + 0);
|
||||
uint16_t F = *(in + 1);
|
||||
uint16_t G = *(in + nextline - 1);
|
||||
uint16_t H = *(in + nextline + 0);
|
||||
uint16_t I = *(in + nextline + 1);
|
||||
uint32_t e = yuvTable[E] + diff_offset;
|
||||
|
||||
uint8_t pattern;
|
||||
pattern = diff(e, A) << 0;
|
||||
pattern |= diff(e, B) << 1;
|
||||
pattern |= diff(e, C) << 2;
|
||||
pattern |= diff(e, D) << 3;
|
||||
pattern |= diff(e, F) << 4;
|
||||
pattern |= diff(e, G) << 5;
|
||||
pattern |= diff(e, H) << 6;
|
||||
pattern |= diff(e, I) << 7;
|
||||
|
||||
*(out0 + 0) = colortable[blend(hqTable[pattern], E, A, B, D, F, H)]; pattern = rotate[pattern];
|
||||
*(out0 + 1) = colortable[blend(hqTable[pattern], E, C, F, B, H, D)]; pattern = rotate[pattern];
|
||||
*(out1 + 1) = colortable[blend(hqTable[pattern], E, I, H, F, D, B)]; pattern = rotate[pattern];
|
||||
*(out1 + 0) = colortable[blend(hqTable[pattern], E, G, D, H, B, F)];
|
||||
|
||||
in++;
|
||||
out0 += 2;
|
||||
out1 += 2;
|
||||
}
|
||||
|
||||
in++;
|
||||
*out0++ = 0; *out0++ = 0;
|
||||
*out1++ = 0; *out1++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
46
bsnes/filter/lq2x.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
namespace Filter::LQ2x {
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width *= 2;
|
||||
height *= 2;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
for(uint y = 0; y < height; y++) {
|
||||
const uint16_t* in = input + y * pitch;
|
||||
uint32_t* out0 = output + y * outpitch * 2;
|
||||
uint32_t* out1 = output + y * outpitch * 2 + outpitch;
|
||||
|
||||
int prevline = (y == 0 ? 0 : pitch);
|
||||
int nextline = (y == height - 1 ? 0 : pitch);
|
||||
|
||||
for(uint x = 0; x < width; x++) {
|
||||
uint16_t A = *(in - prevline);
|
||||
uint16_t B = (x > 0) ? *(in - 1) : *in;
|
||||
uint16_t C = *in;
|
||||
uint16_t D = (x < width - 1) ? *(in + 1) : *in;
|
||||
uint16_t E = *(in++ + nextline);
|
||||
uint32_t c = colortable[C];
|
||||
|
||||
if(A != E && B != D) {
|
||||
*out0++ = (A == B ? colortable[C + A - ((C ^ A) & 0x0421) >> 1] : c);
|
||||
*out0++ = (A == D ? colortable[C + A - ((C ^ A) & 0x0421) >> 1] : c);
|
||||
*out1++ = (E == B ? colortable[C + E - ((C ^ E) & 0x0421) >> 1] : c);
|
||||
*out1++ = (E == D ? colortable[C + E - ((C ^ E) & 0x0421) >> 1] : c);
|
||||
} else {
|
||||
*out0++ = c;
|
||||
*out0++ = c;
|
||||
*out1++ = c;
|
||||
*out1++ = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
24
bsnes/filter/none.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
namespace Filter::None {
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = width;
|
||||
height = height;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
for(uint y = 0; y < height; y++) {
|
||||
const uint16_t* in = input + y * pitch;
|
||||
uint32_t* out = output + y * outpitch;
|
||||
for(uint x = 0; x < width; x++) {
|
||||
*out++ = colortable[*in++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
50
bsnes/filter/ntsc-composite.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
namespace Filter::NTSC_Composite {
|
||||
|
||||
struct snes_ntsc_t *ntsc;
|
||||
snes_ntsc_setup_t setup;
|
||||
int burst;
|
||||
int burst_toggle;
|
||||
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
if(initialized == true) return;
|
||||
initialized = true;
|
||||
|
||||
ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
|
||||
setup = snes_ntsc_composite;
|
||||
setup.merge_fields = 1;
|
||||
snes_ntsc_init(ntsc, &setup);
|
||||
|
||||
burst = 0;
|
||||
burst_toggle = (setup.merge_fields ? 0 : 1);
|
||||
}
|
||||
|
||||
void terminate() {
|
||||
if(ntsc) free(ntsc);
|
||||
}
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = SNES_NTSC_OUT_WIDTH(256);
|
||||
height = height;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable_, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
initialize();
|
||||
colortable = colortable_;
|
||||
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
if(width <= 256) {
|
||||
snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2);
|
||||
} else {
|
||||
snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2);
|
||||
}
|
||||
|
||||
burst ^= burst_toggle;
|
||||
}
|
||||
|
||||
}
|
50
bsnes/filter/ntsc-rf.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
namespace Filter::NTSC_RF {
|
||||
|
||||
struct snes_ntsc_t *ntsc;
|
||||
snes_ntsc_setup_t setup;
|
||||
int burst;
|
||||
int burst_toggle;
|
||||
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
if(initialized == true) return;
|
||||
initialized = true;
|
||||
|
||||
ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
|
||||
setup = snes_ntsc_composite;
|
||||
setup.merge_fields = 0;
|
||||
snes_ntsc_init(ntsc, &setup);
|
||||
|
||||
burst = 0;
|
||||
burst_toggle = (setup.merge_fields ? 0 : 1);
|
||||
}
|
||||
|
||||
void terminate() {
|
||||
if(ntsc) free(ntsc);
|
||||
}
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = SNES_NTSC_OUT_WIDTH(256);
|
||||
height = height;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable_, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
initialize();
|
||||
colortable = colortable_;
|
||||
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
if(width <= 256) {
|
||||
snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2);
|
||||
} else {
|
||||
snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2);
|
||||
}
|
||||
|
||||
burst ^= burst_toggle;
|
||||
}
|
||||
|
||||
}
|
50
bsnes/filter/ntsc-rgb.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
namespace Filter::NTSC_RGB {
|
||||
|
||||
struct snes_ntsc_t *ntsc;
|
||||
snes_ntsc_setup_t setup;
|
||||
int burst;
|
||||
int burst_toggle;
|
||||
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
if(initialized == true) return;
|
||||
initialized = true;
|
||||
|
||||
ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
|
||||
setup = snes_ntsc_rgb;
|
||||
setup.merge_fields = 1;
|
||||
snes_ntsc_init(ntsc, &setup);
|
||||
|
||||
burst = 0;
|
||||
burst_toggle = (setup.merge_fields ? 0 : 1);
|
||||
}
|
||||
|
||||
void terminate() {
|
||||
if(ntsc) free(ntsc);
|
||||
}
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = SNES_NTSC_OUT_WIDTH(256);
|
||||
height = height;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable_, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
initialize();
|
||||
colortable = colortable_;
|
||||
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
if(width <= 256) {
|
||||
snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2);
|
||||
} else {
|
||||
snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2);
|
||||
}
|
||||
|
||||
burst ^= burst_toggle;
|
||||
}
|
||||
|
||||
}
|
50
bsnes/filter/ntsc-svideo.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
namespace Filter::NTSC_SVideo {
|
||||
|
||||
struct snes_ntsc_t *ntsc;
|
||||
snes_ntsc_setup_t setup;
|
||||
int burst;
|
||||
int burst_toggle;
|
||||
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
if(initialized == true) return;
|
||||
initialized = true;
|
||||
|
||||
ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
|
||||
setup = snes_ntsc_svideo;
|
||||
setup.merge_fields = 1;
|
||||
snes_ntsc_init(ntsc, &setup);
|
||||
|
||||
burst = 0;
|
||||
burst_toggle = (setup.merge_fields ? 0 : 1);
|
||||
}
|
||||
|
||||
void terminate() {
|
||||
if(ntsc) free(ntsc);
|
||||
}
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = SNES_NTSC_OUT_WIDTH(256);
|
||||
height = height;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable_, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
initialize();
|
||||
colortable = colortable_;
|
||||
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
if(width <= 256) {
|
||||
snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2);
|
||||
} else {
|
||||
snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2);
|
||||
}
|
||||
|
||||
burst ^= burst_toggle;
|
||||
}
|
||||
|
||||
}
|
40
bsnes/filter/pixellate2x.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
namespace Filter::Pixellate2x {
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = (width <= 256) ? width * 2 : width;
|
||||
height = (height <= 240) ? height * 2 : height;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
uint32_t *out0 = output;
|
||||
uint32_t *out1 = output + outpitch;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
uint32_t p = colortable[*input++];
|
||||
|
||||
*out0++ = p;
|
||||
if(height <= 240) *out1++ = p;
|
||||
if(width > 256) continue;
|
||||
|
||||
*out0++ = p;
|
||||
if(height <= 240) *out1++ = p;
|
||||
}
|
||||
|
||||
input += pitch - width;
|
||||
if(height <= 240) {
|
||||
out0 += outpitch + outpitch - 512;
|
||||
out1 += outpitch + outpitch - 512;
|
||||
} else {
|
||||
out0 += outpitch - 512;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1175
bsnes/filter/sai/sai.cpp
Normal file
46
bsnes/filter/scale2x.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
namespace Filter::Scale2x {
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width *= 2;
|
||||
height *= 2;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
for(uint y = 0; y < height; y++) {
|
||||
const uint16_t* in = input + y * pitch;
|
||||
uint32_t* out0 = output + y * outpitch * 2;
|
||||
uint32_t* out1 = output + y * outpitch * 2 + outpitch;
|
||||
|
||||
int prevline = (y == 0 ? 0 : pitch);
|
||||
int nextline = (y == height - 1 ? 0 : pitch);
|
||||
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
uint16_t A = *(in - prevline);
|
||||
uint16_t B = (x > 0) ? *(in - 1) : *in;
|
||||
uint16_t C = *in;
|
||||
uint16_t D = (x < width - 1) ? *(in + 1) : *in;
|
||||
uint16_t E = *(in++ + nextline);
|
||||
uint32_t c = colortable[C];
|
||||
|
||||
if(A != E && B != D) {
|
||||
*out0++ = (A == B ? colortable[A] : c);
|
||||
*out0++ = (A == D ? colortable[A] : c);
|
||||
*out1++ = (E == B ? colortable[E] : c);
|
||||
*out1++ = (E == D ? colortable[E] : c);
|
||||
} else {
|
||||
*out0++ = c;
|
||||
*out0++ = c;
|
||||
*out1++ = c;
|
||||
*out1++ = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
28
bsnes/filter/scanlines-black.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
namespace Filter::ScanlinesBlack {
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = width;
|
||||
height = height * 2;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* palette, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
const uint16_t *in = input + y * pitch;
|
||||
uint32_t *out0 = output + y * outpitch * 2;
|
||||
uint32_t *out1 = output + y * outpitch * 2 + outpitch;
|
||||
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
uint16_t color = *in++;
|
||||
*out0++ = palette[color];
|
||||
*out1++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
48
bsnes/filter/scanlines-dark.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
namespace Filter::ScanlinesDark {
|
||||
|
||||
uint16_t adjust[32768];
|
||||
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
if(initialized == true) return;
|
||||
initialized = true;
|
||||
|
||||
for(unsigned i = 0; i < 32768; i++) {
|
||||
uint8_t r = (i >> 10) & 31;
|
||||
uint8_t g = (i >> 5) & 31;
|
||||
uint8_t b = (i >> 0) & 31;
|
||||
r *= 0.333;
|
||||
g *= 0.333;
|
||||
b *= 0.333;
|
||||
adjust[i] = (r << 10) + (g << 5) + (b << 0);
|
||||
}
|
||||
}
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = width;
|
||||
height = height * 2;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* palette, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
initialize();
|
||||
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
const uint16_t *in = input + y * pitch;
|
||||
uint32_t *out0 = output + y * outpitch * 2;
|
||||
uint32_t *out1 = output + y * outpitch * 2 + outpitch;
|
||||
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
uint16_t color = *in++;
|
||||
*out0++ = palette[color];
|
||||
*out1++ = palette[adjust[color]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
48
bsnes/filter/scanlines-light.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
namespace Filter::ScanlinesLight {
|
||||
|
||||
uint16_t adjust[32768];
|
||||
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
if(initialized == true) return;
|
||||
initialized = true;
|
||||
|
||||
for(unsigned i = 0; i < 32768; i++) {
|
||||
uint8_t r = (i >> 10) & 31;
|
||||
uint8_t g = (i >> 5) & 31;
|
||||
uint8_t b = (i >> 0) & 31;
|
||||
r *= 0.666;
|
||||
g *= 0.666;
|
||||
b *= 0.666;
|
||||
adjust[i] = (r << 10) + (g << 5) + (b << 0);
|
||||
}
|
||||
}
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width = width;
|
||||
height = height * 2;
|
||||
}
|
||||
|
||||
auto render(
|
||||
uint32_t* palette, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
initialize();
|
||||
|
||||
pitch >>= 1;
|
||||
outpitch >>= 2;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
const uint16_t *in = input + y * pitch;
|
||||
uint32_t *out0 = output + y * outpitch * 2;
|
||||
uint32_t *out1 = output + y * outpitch * 2 + outpitch;
|
||||
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
uint16_t color = *in++;
|
||||
*out0++ = palette[color];
|
||||
*out1++ = palette[adjust[color]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
251
bsnes/filter/snes_ntsc/snes_ntsc.c
Executable file
|
@ -0,0 +1,251 @@
|
|||
/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */
|
||||
|
||||
#include "snes_ntsc.h"
|
||||
|
||||
/* Copyright (C) 2006-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
snes_ntsc_setup_t const snes_ntsc_monochrome = { 0,-1, 0, 0,.2, 0,.2,-.2,-.2,-1, 1, 0, 0 };
|
||||
snes_ntsc_setup_t const snes_ntsc_composite = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 };
|
||||
snes_ntsc_setup_t const snes_ntsc_svideo = { 0, 0, 0, 0,.2, 0,.2, -1, -1, 0, 1, 0, 0 };
|
||||
snes_ntsc_setup_t const snes_ntsc_rgb = { 0, 0, 0, 0,.2, 0,.7, -1, -1,-1, 1, 0, 0 };
|
||||
|
||||
#define alignment_count 3
|
||||
#define burst_count 3
|
||||
#define rescale_in 8
|
||||
#define rescale_out 7
|
||||
|
||||
#define artifacts_mid 1.0f
|
||||
#define fringing_mid 1.0f
|
||||
#define std_decoder_hue 0
|
||||
|
||||
#define rgb_bits 7 /* half normal range to allow for doubled hires pixels */
|
||||
#define gamma_size 32
|
||||
|
||||
#include "snes_ntsc_impl.h"
|
||||
|
||||
/* 3 input pixels -> 8 composite samples */
|
||||
pixel_info_t const snes_ntsc_pixels [alignment_count] = {
|
||||
{ PIXEL_OFFSET( -4, -9 ), { 1, 1, .6667f, 0 } },
|
||||
{ PIXEL_OFFSET( -2, -7 ), { .3333f, 1, 1, .3333f } },
|
||||
{ PIXEL_OFFSET( 0, -5 ), { 0, .6667f, 1, 1 } },
|
||||
};
|
||||
|
||||
static void merge_kernel_fields( snes_ntsc_rgb_t* io )
|
||||
{
|
||||
int n;
|
||||
for ( n = burst_size; n; --n )
|
||||
{
|
||||
snes_ntsc_rgb_t p0 = io [burst_size * 0] + rgb_bias;
|
||||
snes_ntsc_rgb_t p1 = io [burst_size * 1] + rgb_bias;
|
||||
snes_ntsc_rgb_t p2 = io [burst_size * 2] + rgb_bias;
|
||||
/* merge colors without losing precision */
|
||||
io [burst_size * 0] =
|
||||
((p0 + p1 - ((p0 ^ p1) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
|
||||
io [burst_size * 1] =
|
||||
((p1 + p2 - ((p1 ^ p2) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
|
||||
io [burst_size * 2] =
|
||||
((p2 + p0 - ((p2 ^ p0) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
|
||||
++io;
|
||||
}
|
||||
}
|
||||
|
||||
static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out )
|
||||
{
|
||||
int n;
|
||||
for ( n = burst_count; n; --n )
|
||||
{
|
||||
unsigned i;
|
||||
for ( i = 0; i < rgb_kernel_size / 2; i++ )
|
||||
{
|
||||
snes_ntsc_rgb_t error = color -
|
||||
out [i ] - out [(i+12)%14+14] - out [(i+10)%14+28] -
|
||||
out [i + 7] - out [i + 5 +14] - out [i + 3 +28];
|
||||
DISTRIBUTE_ERROR( i+3+28, i+5+14, i+7 );
|
||||
}
|
||||
out += alignment_count * rgb_kernel_size;
|
||||
}
|
||||
}
|
||||
|
||||
void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup )
|
||||
{
|
||||
int merge_fields;
|
||||
int entry;
|
||||
init_t impl;
|
||||
if ( !setup )
|
||||
setup = &snes_ntsc_composite;
|
||||
init( &impl, setup );
|
||||
|
||||
merge_fields = setup->merge_fields;
|
||||
if ( setup->artifacts <= -1 && setup->fringing <= -1 )
|
||||
merge_fields = 1;
|
||||
|
||||
for ( entry = 0; entry < snes_ntsc_palette_size; entry++ )
|
||||
{
|
||||
/* Reduce number of significant bits of source color. Clearing the
|
||||
low bits of R and B were least notictable. Modifying green was too
|
||||
noticeable. */
|
||||
int ir = entry >> 8 & 0x1E;
|
||||
int ig = entry >> 4 & 0x1F;
|
||||
int ib = entry << 1 & 0x1E;
|
||||
|
||||
#if SNES_NTSC_BSNES_COLORTBL
|
||||
if ( setup->bsnes_colortbl )
|
||||
{
|
||||
int bgr15 = (ib << 10) | (ig << 5) | ir;
|
||||
unsigned long rgb16 = setup->bsnes_colortbl [bgr15];
|
||||
ir = rgb16 >> 11 & 0x1E;
|
||||
ig = rgb16 >> 6 & 0x1F;
|
||||
ib = rgb16 & 0x1E;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
float rr = impl.to_float [ir];
|
||||
float gg = impl.to_float [ig];
|
||||
float bb = impl.to_float [ib];
|
||||
|
||||
float y, i, q = RGB_TO_YIQ( rr, gg, bb, y, i );
|
||||
|
||||
int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g );
|
||||
snes_ntsc_rgb_t rgb = PACK_RGB( r, g, b );
|
||||
|
||||
snes_ntsc_rgb_t* out = ntsc->table [entry];
|
||||
gen_kernel( &impl, y, i, q, out );
|
||||
if ( merge_fields )
|
||||
merge_kernel_fields( out );
|
||||
correct_errors( rgb, out );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SNES_NTSC_NO_BLITTERS
|
||||
|
||||
void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
|
||||
int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
|
||||
{
|
||||
int chunk_count = (in_width - 1) / snes_ntsc_in_chunk;
|
||||
for ( ; in_height; --in_height )
|
||||
{
|
||||
SNES_NTSC_IN_T const* line_in = input;
|
||||
SNES_NTSC_BEGIN_ROW( ntsc, burst_phase,
|
||||
snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) );
|
||||
snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
|
||||
int n;
|
||||
++line_in;
|
||||
|
||||
for ( n = chunk_count; n; --n )
|
||||
{
|
||||
/* order of input and output pixels must not be altered */
|
||||
SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
|
||||
SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
|
||||
SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
|
||||
SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
line_in += 3;
|
||||
line_out += 7;
|
||||
}
|
||||
|
||||
/* finish final pixels */
|
||||
SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
|
||||
SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
|
||||
SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
|
||||
SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
|
||||
input += in_row_width;
|
||||
rgb_out = (char*) rgb_out + out_pitch;
|
||||
}
|
||||
}
|
||||
|
||||
void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
|
||||
int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
|
||||
{
|
||||
int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2);
|
||||
for ( ; in_height; --in_height )
|
||||
{
|
||||
SNES_NTSC_IN_T const* line_in = input;
|
||||
SNES_NTSC_HIRES_ROW( ntsc, burst_phase,
|
||||
snes_ntsc_black, snes_ntsc_black, snes_ntsc_black,
|
||||
SNES_NTSC_ADJ_IN( line_in [0] ),
|
||||
SNES_NTSC_ADJ_IN( line_in [1] ) );
|
||||
snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
|
||||
int n;
|
||||
line_in += 2;
|
||||
|
||||
for ( n = chunk_count; n; --n )
|
||||
{
|
||||
/* twice as many input pixels per chunk */
|
||||
SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
|
||||
SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
|
||||
SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
|
||||
SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) );
|
||||
SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) );
|
||||
SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) );
|
||||
SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
line_in += 6;
|
||||
line_out += 7;
|
||||
}
|
||||
|
||||
SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 3, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 4, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 5, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
|
||||
input += in_row_width;
|
||||
rgb_out = (char*) rgb_out + out_pitch;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
228
bsnes/filter/snes_ntsc/snes_ntsc.h
Executable file
|
@ -0,0 +1,228 @@
|
|||
/* SNES NTSC video filter */
|
||||
|
||||
/* snes_ntsc 0.2.2 */
|
||||
#ifndef SNES_NTSC_H
|
||||
#define SNES_NTSC_H
|
||||
|
||||
#include "snes_ntsc_config.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown
|
||||
in parenthesis and should remain fairly stable in future versions. */
|
||||
typedef struct snes_ntsc_setup_t
|
||||
{
|
||||
/* Basic parameters */
|
||||
double hue; /* -1 = -180 degrees +1 = +180 degrees */
|
||||
double saturation; /* -1 = grayscale (0.0) +1 = oversaturated colors (2.0) */
|
||||
double contrast; /* -1 = dark (0.5) +1 = light (1.5) */
|
||||
double brightness; /* -1 = dark (0.5) +1 = light (1.5) */
|
||||
double sharpness; /* edge contrast enhancement/blurring */
|
||||
|
||||
/* Advanced parameters */
|
||||
double gamma; /* -1 = dark (1.5) +1 = light (0.5) */
|
||||
double resolution; /* image resolution */
|
||||
double artifacts; /* artifacts caused by color changes */
|
||||
double fringing; /* color artifacts caused by brightness changes */
|
||||
double bleed; /* color bleed (color resolution reduction) */
|
||||
int merge_fields; /* if 1, merges even and odd fields together to reduce flicker */
|
||||
float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */
|
||||
|
||||
unsigned long const* bsnes_colortbl; /* undocumented; set to 0 */
|
||||
} snes_ntsc_setup_t;
|
||||
|
||||
/* Video format presets */
|
||||
extern snes_ntsc_setup_t const snes_ntsc_composite; /* color bleeding + artifacts */
|
||||
extern snes_ntsc_setup_t const snes_ntsc_svideo; /* color bleeding only */
|
||||
extern snes_ntsc_setup_t const snes_ntsc_rgb; /* crisp image */
|
||||
extern snes_ntsc_setup_t const snes_ntsc_monochrome;/* desaturated + artifacts */
|
||||
|
||||
/* Initializes and adjusts parameters. Can be called multiple times on the same
|
||||
snes_ntsc_t object. Can pass NULL for either parameter. */
|
||||
typedef struct snes_ntsc_t snes_ntsc_t;
|
||||
void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup );
|
||||
|
||||
/* Filters one or more rows of pixels. Input pixel format is set by SNES_NTSC_IN_FORMAT
|
||||
and output RGB depth is set by SNES_NTSC_OUT_DEPTH. Both default to 16-bit RGB.
|
||||
In_row_width is the number of pixels to get to the next input row. Out_pitch
|
||||
is the number of *bytes* to get to the next output row. */
|
||||
void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input,
|
||||
long in_row_width, int burst_phase, int in_width, int in_height,
|
||||
void* rgb_out, long out_pitch );
|
||||
|
||||
void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input,
|
||||
long in_row_width, int burst_phase, int in_width, int in_height,
|
||||
void* rgb_out, long out_pitch );
|
||||
|
||||
/* Number of output pixels written by low-res blitter for given input width. Width
|
||||
might be rounded down slightly; use SNES_NTSC_IN_WIDTH() on result to find rounded
|
||||
value. Guaranteed not to round 256 down at all. */
|
||||
#define SNES_NTSC_OUT_WIDTH( in_width ) \
|
||||
((((in_width) - 1) / snes_ntsc_in_chunk + 1) * snes_ntsc_out_chunk)
|
||||
|
||||
/* Number of low-res input pixels that will fit within given output width. Might be
|
||||
rounded down slightly; use SNES_NTSC_OUT_WIDTH() on result to find rounded
|
||||
value. */
|
||||
#define SNES_NTSC_IN_WIDTH( out_width ) \
|
||||
(((out_width) / snes_ntsc_out_chunk - 1) * snes_ntsc_in_chunk + 1)
|
||||
|
||||
|
||||
/* Interface for user-defined custom blitters */
|
||||
|
||||
enum { snes_ntsc_in_chunk = 3 }; /* number of input pixels read per chunk */
|
||||
enum { snes_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */
|
||||
enum { snes_ntsc_black = 0 }; /* palette index for black */
|
||||
enum { snes_ntsc_burst_count = 3 }; /* burst phase cycles through 0, 1, and 2 */
|
||||
|
||||
/* Begins outputting row and starts three pixels. First pixel will be cut off a bit.
|
||||
Use snes_ntsc_black for unused pixels. Declares variables, so must be before first
|
||||
statement in a block (unless you're using C++). */
|
||||
#define SNES_NTSC_BEGIN_ROW( ntsc, burst, pixel0, pixel1, pixel2 ) \
|
||||
char const* ktable = \
|
||||
(char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\
|
||||
SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, SNES_NTSC_IN_FORMAT, ktable )
|
||||
|
||||
/* Begins input pixel */
|
||||
#define SNES_NTSC_COLOR_IN( index, color ) \
|
||||
SNES_NTSC_COLOR_IN_( index, color, SNES_NTSC_IN_FORMAT, ktable )
|
||||
|
||||
/* Generates output pixel. Bits can be 24, 16, 15, 14, 32 (treated as 24), or 0:
|
||||
24: RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8 RGB)
|
||||
16: RRRRRGGG GGGBBBBB (5-6-5 RGB)
|
||||
15: RRRRRGG GGGBBBBB (5-5-5 RGB)
|
||||
14: BBBBBGG GGGRRRRR (5-5-5 BGR, native SNES format)
|
||||
0: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format; x = junk bits) */
|
||||
#define SNES_NTSC_RGB_OUT( index, rgb_out, bits ) \
|
||||
SNES_NTSC_RGB_OUT_14_( index, rgb_out, bits, 1 )
|
||||
|
||||
/* Hires equivalents */
|
||||
#define SNES_NTSC_HIRES_ROW( ntsc, burst, pixel1, pixel2, pixel3, pixel4, pixel5 ) \
|
||||
char const* ktable = \
|
||||
(char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\
|
||||
unsigned const snes_ntsc_pixel1_ = (pixel1);\
|
||||
snes_ntsc_rgb_t const* kernel1 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel1_ );\
|
||||
unsigned const snes_ntsc_pixel2_ = (pixel2);\
|
||||
snes_ntsc_rgb_t const* kernel2 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel2_ );\
|
||||
unsigned const snes_ntsc_pixel3_ = (pixel3);\
|
||||
snes_ntsc_rgb_t const* kernel3 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel3_ );\
|
||||
unsigned const snes_ntsc_pixel4_ = (pixel4);\
|
||||
snes_ntsc_rgb_t const* kernel4 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel4_ );\
|
||||
unsigned const snes_ntsc_pixel5_ = (pixel5);\
|
||||
snes_ntsc_rgb_t const* kernel5 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel5_ );\
|
||||
snes_ntsc_rgb_t const* kernel0 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx0;\
|
||||
snes_ntsc_rgb_t const* kernelx1 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx2 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx3 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx4 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx5 = kernel1
|
||||
|
||||
#define SNES_NTSC_HIRES_OUT( x, rgb_out, bits ) {\
|
||||
snes_ntsc_rgb_t raw_ =\
|
||||
kernel0 [ x ] + kernel2 [(x+5)%7+14] + kernel4 [(x+3)%7+28] +\
|
||||
kernelx0 [(x+7)%7+7] + kernelx2 [(x+5)%7+21] + kernelx4 [(x+3)%7+35] +\
|
||||
kernel1 [(x+6)%7 ] + kernel3 [(x+4)%7+14] + kernel5 [(x+2)%7+28] +\
|
||||
kernelx1 [(x+6)%7+7] + kernelx3 [(x+4)%7+21] + kernelx5 [(x+2)%7+35];\
|
||||
SNES_NTSC_CLAMP_( raw_, 0 );\
|
||||
SNES_NTSC_RGB_OUT_( rgb_out, (bits), 0 );\
|
||||
}
|
||||
|
||||
|
||||
/* private */
|
||||
enum { snes_ntsc_entry_size = 128 };
|
||||
enum { snes_ntsc_palette_size = 0x2000 };
|
||||
typedef unsigned long snes_ntsc_rgb_t;
|
||||
struct snes_ntsc_t {
|
||||
snes_ntsc_rgb_t table [snes_ntsc_palette_size] [snes_ntsc_entry_size];
|
||||
};
|
||||
enum { snes_ntsc_burst_size = snes_ntsc_entry_size / snes_ntsc_burst_count };
|
||||
|
||||
#define SNES_NTSC_RGB16( ktable, n ) \
|
||||
(snes_ntsc_rgb_t const*) (ktable + ((n & 0x001E) | (n >> 1 & 0x03E0) | (n >> 2 & 0x3C00)) * \
|
||||
(snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t)))
|
||||
|
||||
#define SNES_NTSC_BGR15( ktable, n ) \
|
||||
(snes_ntsc_rgb_t const*) (ktable + ((n << 9 & 0x3C00) | (n & 0x03E0) | (n >> 10 & 0x001E)) * \
|
||||
(snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t)))
|
||||
|
||||
/* common 3->7 ntsc macros */
|
||||
#define SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, ENTRY, table ) \
|
||||
unsigned const snes_ntsc_pixel0_ = (pixel0);\
|
||||
snes_ntsc_rgb_t const* kernel0 = ENTRY( table, snes_ntsc_pixel0_ );\
|
||||
unsigned const snes_ntsc_pixel1_ = (pixel1);\
|
||||
snes_ntsc_rgb_t const* kernel1 = ENTRY( table, snes_ntsc_pixel1_ );\
|
||||
unsigned const snes_ntsc_pixel2_ = (pixel2);\
|
||||
snes_ntsc_rgb_t const* kernel2 = ENTRY( table, snes_ntsc_pixel2_ );\
|
||||
snes_ntsc_rgb_t const* kernelx0;\
|
||||
snes_ntsc_rgb_t const* kernelx1 = kernel0;\
|
||||
snes_ntsc_rgb_t const* kernelx2 = kernel0
|
||||
|
||||
#define SNES_NTSC_RGB_OUT_14_( x, rgb_out, bits, shift ) {\
|
||||
snes_ntsc_rgb_t raw_ =\
|
||||
kernel0 [x ] + kernel1 [(x+12)%7+14] + kernel2 [(x+10)%7+28] +\
|
||||
kernelx0 [(x+7)%14] + kernelx1 [(x+ 5)%7+21] + kernelx2 [(x+ 3)%7+35];\
|
||||
SNES_NTSC_CLAMP_( raw_, shift );\
|
||||
SNES_NTSC_RGB_OUT_( rgb_out, bits, shift );\
|
||||
}
|
||||
|
||||
/* common ntsc macros */
|
||||
#define snes_ntsc_rgb_builder ((1L << 21) | (1 << 11) | (1 << 1))
|
||||
#define snes_ntsc_clamp_mask (snes_ntsc_rgb_builder * 3 / 2)
|
||||
#define snes_ntsc_clamp_add (snes_ntsc_rgb_builder * 0x101)
|
||||
#define SNES_NTSC_CLAMP_( io, shift ) {\
|
||||
snes_ntsc_rgb_t sub = (io) >> (9-(shift)) & snes_ntsc_clamp_mask;\
|
||||
snes_ntsc_rgb_t clamp = snes_ntsc_clamp_add - sub;\
|
||||
io |= clamp;\
|
||||
clamp -= sub;\
|
||||
io &= clamp;\
|
||||
}
|
||||
|
||||
#define SNES_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\
|
||||
unsigned color_;\
|
||||
kernelx##index = kernel##index;\
|
||||
kernel##index = (color_ = (color), ENTRY( table, color_ ));\
|
||||
}
|
||||
|
||||
/* x is always zero except in snes_ntsc library */
|
||||
/* original routine */
|
||||
/*
|
||||
#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\
|
||||
if ( bits == 16 )\
|
||||
rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\
|
||||
if ( bits == 24 || bits == 32 )\
|
||||
rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\
|
||||
if ( bits == 15 )\
|
||||
rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\
|
||||
if ( bits == 14 )\
|
||||
rgb_out = (raw_>>(24-x)& 0x001F)|(raw_>>(9-x)&0x03E0)|(raw_<<(6+x)&0x7C00);\
|
||||
if ( bits == 0 )\
|
||||
rgb_out = raw_ << x;\
|
||||
}
|
||||
*/
|
||||
|
||||
/* custom bsnes routine -- hooks into bsnes colortable */
|
||||
#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\
|
||||
if ( bits == 16 ) {\
|
||||
rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\
|
||||
rgb_out = ((rgb_out&0xf800)>>11)|((rgb_out&0x07c0)>>1)|((rgb_out&0x001f)<<10);\
|
||||
rgb_out = colortable[rgb_out];\
|
||||
} else if ( bits == 24 || bits == 32 ) {\
|
||||
rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\
|
||||
rgb_out = ((rgb_out&0xf80000)>>19)|((rgb_out&0x00f800)>>6)|((rgb_out&0x0000f8)<<7);\
|
||||
rgb_out = colortable[rgb_out];\
|
||||
} else if ( bits == 15 ) {\
|
||||
rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\
|
||||
rgb_out = ((rgb_out&0x7c00)>>10)|((rgb_out&0x03e0))|((rgb_out&0x001f)<<10);\
|
||||
rgb_out = colortable[rgb_out];\
|
||||
} else {\
|
||||
rgb_out = raw_ << x;\
|
||||
}\
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
26
bsnes/filter/snes_ntsc/snes_ntsc_config.h
Executable file
|
@ -0,0 +1,26 @@
|
|||
/* Configure library by modifying this file */
|
||||
|
||||
#ifndef SNES_NTSC_CONFIG_H
|
||||
#define SNES_NTSC_CONFIG_H
|
||||
|
||||
/* Format of source pixels */
|
||||
/* #define SNES_NTSC_IN_FORMAT SNES_NTSC_RGB16 */
|
||||
#define SNES_NTSC_IN_FORMAT SNES_NTSC_BGR15
|
||||
|
||||
/* The following affect the built-in blitter only; a custom blitter can
|
||||
handle things however it wants. */
|
||||
|
||||
/* Bits per pixel of output. Can be 15, 16, 32, or 24 (same as 32). */
|
||||
#define SNES_NTSC_OUT_DEPTH 32
|
||||
|
||||
/* Type of input pixel values */
|
||||
#define SNES_NTSC_IN_T unsigned short
|
||||
|
||||
/* Each raw pixel input value is passed through this. You might want to mask
|
||||
the pixel index if you use the high bits as flags, etc. */
|
||||
#define SNES_NTSC_ADJ_IN( in ) in
|
||||
|
||||
/* For each pixel, this is the basic operation:
|
||||
output_color = SNES_NTSC_ADJ_IN( SNES_NTSC_IN_T ) */
|
||||
|
||||
#endif
|
439
bsnes/filter/snes_ntsc/snes_ntsc_impl.h
Executable file
|
@ -0,0 +1,439 @@
|
|||
/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */
|
||||
|
||||
/* Common implementation of NTSC filters */
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Copyright (C) 2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#define DISABLE_CORRECTION 0
|
||||
|
||||
#undef PI
|
||||
#define PI 3.14159265358979323846f
|
||||
|
||||
#ifndef LUMA_CUTOFF
|
||||
#define LUMA_CUTOFF 0.20
|
||||
#endif
|
||||
#ifndef gamma_size
|
||||
#define gamma_size 1
|
||||
#endif
|
||||
#ifndef rgb_bits
|
||||
#define rgb_bits 8
|
||||
#endif
|
||||
#ifndef artifacts_max
|
||||
#define artifacts_max (artifacts_mid * 1.5f)
|
||||
#endif
|
||||
#ifndef fringing_max
|
||||
#define fringing_max (fringing_mid * 2)
|
||||
#endif
|
||||
#ifndef STD_HUE_CONDITION
|
||||
#define STD_HUE_CONDITION( setup ) 1
|
||||
#endif
|
||||
|
||||
#define ext_decoder_hue (std_decoder_hue + 15)
|
||||
#define rgb_unit (1 << rgb_bits)
|
||||
#define rgb_offset (rgb_unit * 2 + 0.5f)
|
||||
|
||||
enum { burst_size = snes_ntsc_entry_size / burst_count };
|
||||
enum { kernel_half = 16 };
|
||||
enum { kernel_size = kernel_half * 2 + 1 };
|
||||
|
||||
typedef struct init_t
|
||||
{
|
||||
float to_rgb [burst_count * 6];
|
||||
float to_float [gamma_size];
|
||||
float contrast;
|
||||
float brightness;
|
||||
float artifacts;
|
||||
float fringing;
|
||||
float kernel [rescale_out * kernel_size * 2];
|
||||
} init_t;
|
||||
|
||||
#define ROTATE_IQ( i, q, sin_b, cos_b ) {\
|
||||
float t;\
|
||||
t = i * cos_b - q * sin_b;\
|
||||
q = i * sin_b + q * cos_b;\
|
||||
i = t;\
|
||||
}
|
||||
|
||||
static void init_filters( init_t* impl, snes_ntsc_setup_t const* setup )
|
||||
{
|
||||
#if rescale_out > 1
|
||||
float kernels [kernel_size * 2];
|
||||
#else
|
||||
float* const kernels = impl->kernel;
|
||||
#endif
|
||||
|
||||
/* generate luma (y) filter using sinc kernel */
|
||||
{
|
||||
/* sinc with rolloff (dsf) */
|
||||
float const rolloff = 1 + (float) setup->sharpness * (float) 0.032;
|
||||
float const maxh = 32;
|
||||
float const pow_a_n = (float) pow( rolloff, maxh );
|
||||
float sum;
|
||||
int i;
|
||||
/* quadratic mapping to reduce negative (blurring) range */
|
||||
float to_angle = (float) setup->resolution + 1;
|
||||
to_angle = PI / maxh * (float) LUMA_CUTOFF * (to_angle * to_angle + 1);
|
||||
|
||||
kernels [kernel_size * 3 / 2] = maxh; /* default center value */
|
||||
for ( i = 0; i < kernel_half * 2 + 1; i++ )
|
||||
{
|
||||
int x = i - kernel_half;
|
||||
float angle = x * to_angle;
|
||||
/* instability occurs at center point with rolloff very close to 1.0 */
|
||||
if ( x || pow_a_n > (float) 1.056 || pow_a_n < (float) 0.981 )
|
||||
{
|
||||
float rolloff_cos_a = rolloff * (float) cos( angle );
|
||||
float num = 1 - rolloff_cos_a -
|
||||
pow_a_n * (float) cos( maxh * angle ) +
|
||||
pow_a_n * rolloff * (float) cos( (maxh - 1) * angle );
|
||||
float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
|
||||
float dsf = num / den;
|
||||
kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - (float) 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* apply blackman window and find sum */
|
||||
sum = 0;
|
||||
for ( i = 0; i < kernel_half * 2 + 1; i++ )
|
||||
{
|
||||
float x = PI * 2 / (kernel_half * 2) * i;
|
||||
float blackman = 0.42f - 0.5f * (float) cos( x ) + 0.08f * (float) cos( x * 2 );
|
||||
sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman);
|
||||
}
|
||||
|
||||
/* normalize kernel */
|
||||
sum = 1.0f / sum;
|
||||
for ( i = 0; i < kernel_half * 2 + 1; i++ )
|
||||
{
|
||||
int x = kernel_size * 3 / 2 - kernel_half + i;
|
||||
kernels [x] *= sum;
|
||||
assert( kernels [x] == kernels [x] ); /* catch numerical instability */
|
||||
}
|
||||
}
|
||||
|
||||
/* generate chroma (iq) filter using gaussian kernel */
|
||||
{
|
||||
float const cutoff_factor = -0.03125f;
|
||||
float cutoff = (float) setup->bleed;
|
||||
int i;
|
||||
|
||||
if ( cutoff < 0 )
|
||||
{
|
||||
/* keep extreme value accessible only near upper end of scale (1.0) */
|
||||
cutoff *= cutoff;
|
||||
cutoff *= cutoff;
|
||||
cutoff *= cutoff;
|
||||
cutoff *= -30.0f / 0.65f;
|
||||
}
|
||||
cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff;
|
||||
|
||||
for ( i = -kernel_half; i <= kernel_half; i++ )
|
||||
kernels [kernel_size / 2 + i] = (float) exp( i * i * cutoff );
|
||||
|
||||
/* normalize even and odd phases separately */
|
||||
for ( i = 0; i < 2; i++ )
|
||||
{
|
||||
float sum = 0;
|
||||
int x;
|
||||
for ( x = i; x < kernel_size; x += 2 )
|
||||
sum += kernels [x];
|
||||
|
||||
sum = 1.0f / sum;
|
||||
for ( x = i; x < kernel_size; x += 2 )
|
||||
{
|
||||
kernels [x] *= sum;
|
||||
assert( kernels [x] == kernels [x] ); /* catch numerical instability */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
printf( "luma:\n" );
|
||||
for ( i = kernel_size; i < kernel_size * 2; i++ )
|
||||
printf( "%f\n", kernels [i] );
|
||||
printf( "chroma:\n" );
|
||||
for ( i = 0; i < kernel_size; i++ )
|
||||
printf( "%f\n", kernels [i] );
|
||||
*/
|
||||
|
||||
/* generate linear rescale kernels */
|
||||
#if rescale_out > 1
|
||||
{
|
||||
float weight = 1.0f;
|
||||
float* out = impl->kernel;
|
||||
int n = rescale_out;
|
||||
do
|
||||
{
|
||||
float remain = 0;
|
||||
int i;
|
||||
weight -= 1.0f / rescale_in;
|
||||
for ( i = 0; i < kernel_size * 2; i++ )
|
||||
{
|
||||
float cur = kernels [i];
|
||||
float m = cur * weight;
|
||||
*out++ = m + remain;
|
||||
remain = cur - m;
|
||||
}
|
||||
}
|
||||
while ( --n );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static float const default_decoder [6] =
|
||||
{ 0.956f, 0.621f, -0.272f, -0.647f, -1.105f, 1.702f };
|
||||
|
||||
static void init( init_t* impl, snes_ntsc_setup_t const* setup )
|
||||
{
|
||||
impl->brightness = (float) setup->brightness * (0.5f * rgb_unit) + rgb_offset;
|
||||
impl->contrast = (float) setup->contrast * (0.5f * rgb_unit) + rgb_unit;
|
||||
#ifdef default_palette_contrast
|
||||
if ( !setup->palette )
|
||||
impl->contrast *= default_palette_contrast;
|
||||
#endif
|
||||
|
||||
impl->artifacts = (float) setup->artifacts;
|
||||
if ( impl->artifacts > 0 )
|
||||
impl->artifacts *= artifacts_max - artifacts_mid;
|
||||
impl->artifacts = impl->artifacts * artifacts_mid + artifacts_mid;
|
||||
|
||||
impl->fringing = (float) setup->fringing;
|
||||
if ( impl->fringing > 0 )
|
||||
impl->fringing *= fringing_max - fringing_mid;
|
||||
impl->fringing = impl->fringing * fringing_mid + fringing_mid;
|
||||
|
||||
init_filters( impl, setup );
|
||||
|
||||
/* generate gamma table */
|
||||
if ( gamma_size > 1 )
|
||||
{
|
||||
float const to_float = 1.0f / (gamma_size - (gamma_size > 1));
|
||||
float const gamma = 1.1333f - (float) setup->gamma * 0.5f;
|
||||
/* match common PC's 2.2 gamma to TV's 2.65 gamma */
|
||||
int i;
|
||||
for ( i = 0; i < gamma_size; i++ )
|
||||
impl->to_float [i] =
|
||||
(float) pow( i * to_float, gamma ) * impl->contrast + impl->brightness;
|
||||
}
|
||||
|
||||
/* setup decoder matricies */
|
||||
{
|
||||
float hue = (float) setup->hue * PI + PI / 180 * ext_decoder_hue;
|
||||
float sat = (float) setup->saturation + 1;
|
||||
float const* decoder = setup->decoder_matrix;
|
||||
if ( !decoder )
|
||||
{
|
||||
decoder = default_decoder;
|
||||
if ( STD_HUE_CONDITION( setup ) )
|
||||
hue += PI / 180 * (std_decoder_hue - ext_decoder_hue);
|
||||
}
|
||||
|
||||
{
|
||||
float s = (float) sin( hue ) * sat;
|
||||
float c = (float) cos( hue ) * sat;
|
||||
float* out = impl->to_rgb;
|
||||
int n;
|
||||
|
||||
n = burst_count;
|
||||
do
|
||||
{
|
||||
float const* in = decoder;
|
||||
int n = 3;
|
||||
do
|
||||
{
|
||||
float i = *in++;
|
||||
float q = *in++;
|
||||
*out++ = i * c - q * s;
|
||||
*out++ = i * s + q * c;
|
||||
}
|
||||
while ( --n );
|
||||
if ( burst_count <= 1 )
|
||||
break;
|
||||
ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */
|
||||
}
|
||||
while ( --n );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* kernel generation */
|
||||
|
||||
#define RGB_TO_YIQ( r, g, b, y, i ) (\
|
||||
(y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\
|
||||
(i = (r) * 0.596f - (g) * 0.275f - (b) * 0.321f),\
|
||||
((r) * 0.212f - (g) * 0.523f + (b) * 0.311f)\
|
||||
)
|
||||
|
||||
#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\
|
||||
r = (type) (y + to_rgb [0] * i + to_rgb [1] * q),\
|
||||
g = (type) (y + to_rgb [2] * i + to_rgb [3] * q),\
|
||||
(type) (y + to_rgb [4] * i + to_rgb [5] * q)\
|
||||
)
|
||||
|
||||
#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1)
|
||||
|
||||
enum { rgb_kernel_size = burst_size / alignment_count };
|
||||
enum { rgb_bias = rgb_unit * 2 * snes_ntsc_rgb_builder };
|
||||
|
||||
typedef struct pixel_info_t
|
||||
{
|
||||
int offset;
|
||||
float negate;
|
||||
float kernel [4];
|
||||
} pixel_info_t;
|
||||
|
||||
#if rescale_in > 1
|
||||
#define PIXEL_OFFSET_( ntsc, scaled ) \
|
||||
(kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \
|
||||
(kernel_size * 2 * scaled))
|
||||
|
||||
#define PIXEL_OFFSET( ntsc, scaled ) \
|
||||
PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\
|
||||
(((scaled) + rescale_out * 10) % rescale_out) ),\
|
||||
(1.0f - (((ntsc) + 100) & 2))
|
||||
#else
|
||||
#define PIXEL_OFFSET( ntsc, scaled ) \
|
||||
(kernel_size / 2 + (ntsc) - (scaled)),\
|
||||
(1.0f - (((ntsc) + 100) & 2))
|
||||
#endif
|
||||
|
||||
extern pixel_info_t const snes_ntsc_pixels [alignment_count];
|
||||
|
||||
/* Generate pixel at all burst phases and column alignments */
|
||||
static void gen_kernel( init_t* impl, float y, float i, float q, snes_ntsc_rgb_t* out )
|
||||
{
|
||||
/* generate for each scanline burst phase */
|
||||
float const* to_rgb = impl->to_rgb;
|
||||
int burst_remain = burst_count;
|
||||
y -= rgb_offset;
|
||||
do
|
||||
{
|
||||
/* Encode yiq into *two* composite signals (to allow control over artifacting).
|
||||
Convolve these with kernels which: filter respective components, apply
|
||||
sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack
|
||||
into integer. Based on algorithm by NewRisingSun. */
|
||||
pixel_info_t const* pixel = snes_ntsc_pixels;
|
||||
int alignment_remain = alignment_count;
|
||||
do
|
||||
{
|
||||
/* negate is -1 when composite starts at odd multiple of 2 */
|
||||
float const yy = y * impl->fringing * pixel->negate;
|
||||
float const ic0 = (i + yy) * pixel->kernel [0];
|
||||
float const qc1 = (q + yy) * pixel->kernel [1];
|
||||
float const ic2 = (i - yy) * pixel->kernel [2];
|
||||
float const qc3 = (q - yy) * pixel->kernel [3];
|
||||
|
||||
float const factor = impl->artifacts * pixel->negate;
|
||||
float const ii = i * factor;
|
||||
float const yc0 = (y + ii) * pixel->kernel [0];
|
||||
float const yc2 = (y - ii) * pixel->kernel [2];
|
||||
|
||||
float const qq = q * factor;
|
||||
float const yc1 = (y + qq) * pixel->kernel [1];
|
||||
float const yc3 = (y - qq) * pixel->kernel [3];
|
||||
|
||||
float const* k = &impl->kernel [pixel->offset];
|
||||
int n;
|
||||
++pixel;
|
||||
for ( n = rgb_kernel_size; n; --n )
|
||||
{
|
||||
float i = k[0]*ic0 + k[2]*ic2;
|
||||
float q = k[1]*qc1 + k[3]*qc3;
|
||||
float y = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 +
|
||||
k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset;
|
||||
if ( rescale_out <= 1 )
|
||||
k--;
|
||||
else if ( k < &impl->kernel [kernel_size * 2 * (rescale_out - 1)] )
|
||||
k += kernel_size * 2 - 1;
|
||||
else
|
||||
k -= kernel_size * 2 * (rescale_out - 1) + 2;
|
||||
{
|
||||
int r, g, b = YIQ_TO_RGB( y, i, q, to_rgb, int, r, g );
|
||||
*out++ = PACK_RGB( r, g, b ) - rgb_bias;
|
||||
}
|
||||
}
|
||||
}
|
||||
while ( alignment_count > 1 && --alignment_remain );
|
||||
|
||||
if ( burst_count <= 1 )
|
||||
break;
|
||||
|
||||
to_rgb += 6;
|
||||
|
||||
ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */
|
||||
}
|
||||
while ( --burst_remain );
|
||||
}
|
||||
|
||||
static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out );
|
||||
|
||||
#if DISABLE_CORRECTION
|
||||
#define CORRECT_ERROR( a ) { out [i] += rgb_bias; }
|
||||
#define DISTRIBUTE_ERROR( a, b, c ) { out [i] += rgb_bias; }
|
||||
#else
|
||||
#define CORRECT_ERROR( a ) { out [a] += error; }
|
||||
#define DISTRIBUTE_ERROR( a, b, c ) {\
|
||||
snes_ntsc_rgb_t fourth = (error + 2 * snes_ntsc_rgb_builder) >> 2;\
|
||||
fourth &= (rgb_bias >> 1) - snes_ntsc_rgb_builder;\
|
||||
fourth -= rgb_bias >> 2;\
|
||||
out [a] += fourth;\
|
||||
out [b] += fourth;\
|
||||
out [c] += fourth;\
|
||||
out [i] += error - (fourth * 3);\
|
||||
}
|
||||
#endif
|
||||
|
||||
#define RGB_PALETTE_OUT( rgb, out_ )\
|
||||
{\
|
||||
unsigned char* out = (out_);\
|
||||
snes_ntsc_rgb_t clamped = (rgb);\
|
||||
SNES_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\
|
||||
out [0] = (unsigned char) (clamped >> 21);\
|
||||
out [1] = (unsigned char) (clamped >> 11);\
|
||||
out [2] = (unsigned char) (clamped >> 1);\
|
||||
}
|
||||
|
||||
/* blitter related */
|
||||
|
||||
#ifndef restrict
|
||||
#if defined (__GNUC__)
|
||||
#define restrict __restrict__
|
||||
#elif defined (_MSC_VER) && _MSC_VER > 1300
|
||||
#define restrict __restrict
|
||||
#else
|
||||
/* no support for restricted pointers */
|
||||
#define restrict
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#if SNES_NTSC_OUT_DEPTH <= 16
|
||||
#if USHRT_MAX == 0xFFFF
|
||||
typedef unsigned short snes_ntsc_out_t;
|
||||
#else
|
||||
#error "Need 16-bit int type"
|
||||
#endif
|
||||
|
||||
#else
|
||||
#if UINT_MAX == 0xFFFFFFFF
|
||||
typedef unsigned int snes_ntsc_out_t;
|
||||
#elif ULONG_MAX == 0xFFFFFFFF
|
||||
typedef unsigned long snes_ntsc_out_t;
|
||||
#else
|
||||
#error "Need 32-bit int type"
|
||||
#endif
|
||||
|
||||
#endif
|
25
bsnes/filter/super-2xsai.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
namespace Filter::Super2xSaI {
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width *= 2;
|
||||
height *= 2;
|
||||
}
|
||||
|
||||
uint32_t temp[512 * 480];
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
const uint16_t *line_in = (const uint16_t*)(((const uint8_t*)input) + pitch * y);
|
||||
uint32_t *line_out = temp + y * width;
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
line_out[x] = colortable[line_in[x]];
|
||||
}
|
||||
}
|
||||
|
||||
Super2xSaI32((unsigned char*)temp, width * sizeof(uint32_t), 0, (unsigned char*)output, outpitch, width, height);
|
||||
}
|
||||
|
||||
}
|
25
bsnes/filter/super-eagle.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
namespace Filter::SuperEagle {
|
||||
|
||||
auto size(uint& width, uint& height) -> void {
|
||||
width *= 2;
|
||||
height *= 2;
|
||||
}
|
||||
|
||||
uint32_t temp[512 * 480];
|
||||
|
||||
auto render(
|
||||
uint32_t* colortable, uint32_t* output, uint outpitch,
|
||||
const uint16_t* input, uint pitch, uint width, uint height
|
||||
) -> void {
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
const uint16_t *line_in = (const uint16_t*)(((const uint8_t*)input) + pitch * y);
|
||||
uint32_t *line_out = temp + y * width;
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
line_out[x] = colortable[line_in[x]];
|
||||
}
|
||||
}
|
||||
|
||||
SuperEagle32((unsigned char*)temp, width * sizeof(uint32_t), 0, (unsigned char*)output, outpitch, width, height);
|
||||
}
|
||||
|
||||
}
|
10
bsnes/gb/.gitattributes
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
# 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
|
25
bsnes/gb/.github/actions/LICENSE
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
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.
|
BIN
bsnes/gb/.github/actions/cgb-acid2.gbc
vendored
Normal file
BIN
bsnes/gb/.github/actions/cgb_sound.gb
vendored
Normal file
BIN
bsnes/gb/.github/actions/dmg-acid2.gb
vendored
Normal file
BIN
bsnes/gb/.github/actions/dmg_sound-2.gb
vendored
Executable file
23
bsnes/gb/.github/actions/install_deps.sh
vendored
Executable file
|
@ -0,0 +1,23 @@
|
|||
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
|
BIN
bsnes/gb/.github/actions/oam_bug-2.gb
vendored
Executable file
33
bsnes/gb/.github/actions/sanity_tests.sh
vendored
Executable file
|
@ -0,0 +1,33 @@
|
|||
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
|
36
bsnes/gb/.github/workflows/sanity.yml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
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
Normal file
|
@ -0,0 +1 @@
|
|||
build
|
217
bsnes/gb/BESS.md
Normal file
|
@ -0,0 +1,217 @@
|
|||
# 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
|
BIN
bsnes/gb/BootROMs/SameBoyLogo.png
Normal file
After Width: | Height: | Size: 479 B |
2
bsnes/gb/BootROMs/agb_boot.asm
Normal file
|
@ -0,0 +1,2 @@
|
|||
AGB EQU 1
|
||||
include "cgb_boot.asm"
|
1241
bsnes/gb/BootROMs/cgb_boot.asm
Normal file
2
bsnes/gb/BootROMs/cgb_boot_fast.asm
Normal file
|
@ -0,0 +1,2 @@
|
|||
FAST EQU 1
|
||||
include "cgb_boot.asm"
|
177
bsnes/gb/BootROMs/dmg_boot.asm
Normal file
|
@ -0,0 +1,177 @@
|
|||
; 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
|
102
bsnes/gb/BootROMs/pb12.c
Normal file
|
@ -0,0 +1,102 @@
|
|||
#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;
|
||||
}
|
2
bsnes/gb/BootROMs/sgb2_boot.asm
Normal file
|
@ -0,0 +1,2 @@
|
|||
SGB2 EQU 1
|
||||
include "sgb_boot.asm"
|
213
bsnes/gb/BootROMs/sgb_boot.asm
Normal file
|
@ -0,0 +1,213 @@
|
|||
; 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
|
1
bsnes/gb/CHANGES.md
Normal file
|
@ -0,0 +1 @@
|
|||
See https://sameboy.github.io/changelog/
|
79
bsnes/gb/CONTRIBUTING.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# 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.
|
25
bsnes/gb/Cocoa/AppDelegate.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#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
|
||||
|
449
bsnes/gb/Cocoa/AppDelegate.m
Normal file
|
@ -0,0 +1,449 @@
|
|||
#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
|
BIN
bsnes/gb/Cocoa/AppIcon.icns
Normal file
30
bsnes/gb/Cocoa/BigSurToolbar.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#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
|
BIN
bsnes/gb/Cocoa/CPU.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
bsnes/gb/Cocoa/CPU@2x.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
bsnes/gb/Cocoa/Cartridge.icns
Normal file
BIN
bsnes/gb/Cocoa/ColorCartridge.icns
Normal file
BIN
bsnes/gb/Cocoa/Display.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
bsnes/gb/Cocoa/Display@2x.png
Normal file
After Width: | Height: | Size: 20 KiB |
61
bsnes/gb/Cocoa/Document.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#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
|
||||
|
2315
bsnes/gb/Cocoa/Document.m
Normal file
1088
bsnes/gb/Cocoa/Document.xib
Normal file
12
bsnes/gb/Cocoa/GBAudioClient.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#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
|
111
bsnes/gb/Cocoa/GBAudioClient.m
Normal file
|
@ -0,0 +1,111 @@
|
|||
#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
|
5
bsnes/gb/Cocoa/GBBorderView.h
Normal file
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBBorderView : NSView
|
||||
|
||||
@end
|
26
bsnes/gb/Cocoa/GBBorderView.m
Normal file
|
@ -0,0 +1,26 @@
|
|||
#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
|
35
bsnes/gb/Cocoa/GBButtons.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#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
|
4
bsnes/gb/Cocoa/GBButtons.m
Normal file
|
@ -0,0 +1,4 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "GBButtons.h"
|
||||
|
||||
NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion"};
|
5
bsnes/gb/Cocoa/GBCheatTextFieldCell.h
Normal file
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBCheatTextFieldCell : NSTextFieldCell
|
||||
@property (nonatomic) bool usesAddressFormat;
|
||||
@end
|
121
bsnes/gb/Cocoa/GBCheatTextFieldCell.m
Normal file
|
@ -0,0 +1,121 @@
|
|||
#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
|
16
bsnes/gb/Cocoa/GBCheatWindowController.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#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
|
240
bsnes/gb/Cocoa/GBCheatWindowController.m
Normal file
|
@ -0,0 +1,240 @@
|
|||
#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
|