mirror of
https://github.com/ares-emulator/ares.git
synced 2026-11-09 09:19:42 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fd379613a | ||
|
|
aabf52d678 | ||
|
|
c9ca01fe20 | ||
|
|
becf122aaa | ||
|
|
1e130d7872 | ||
|
|
c57c733d7d | ||
|
|
e41aa25887 | ||
|
|
435f7d4371 | ||
|
|
a1980fab09 | ||
|
|
ebb234ba5f | ||
|
|
2cc7fe30b4 | ||
|
|
5c3c872b78 | ||
|
|
36bf915244 | ||
|
|
045a0f7e79 | ||
|
|
a209e6ffbe | ||
|
|
3bf672dd97 | ||
|
|
157ddf3e8f | ||
|
|
ea23bf53ae | ||
|
|
d4598e1d01 | ||
|
|
f9a8564af0 | ||
|
|
6d66b1136d | ||
|
|
b01f18c34c | ||
|
|
1ebdb69516 | ||
|
|
add0f74387 | ||
|
|
b20f70f333 | ||
|
|
9aebf7bc6b | ||
|
|
a7bf219d5d | ||
|
|
04118be59a | ||
|
|
f24d17859f | ||
|
|
35fd80bde7 | ||
|
|
ccf1c00b58 | ||
|
|
f4520d41ec | ||
|
|
e308cf4275 | ||
|
|
192e53bb87 | ||
|
|
0ed9edfcdb | ||
|
|
764fe1974a | ||
|
|
a55d640459 | ||
|
|
6010bffe5d | ||
|
|
e492268025 | ||
|
|
a36c26c997 | ||
|
|
a3945e5772 | ||
|
|
6b6233b3af | ||
|
|
9f63cb1b99 | ||
|
|
49c39e0e4d | ||
|
|
7dec0b2a3c | ||
|
|
f288280ceb | ||
|
|
c6c5f4669c | ||
|
|
397b9c4505 | ||
|
|
7e2cfb6d40 | ||
|
|
970dcea0ac | ||
|
|
402c146a53 | ||
|
|
a471c150c9 | ||
|
|
ea38ea2537 | ||
|
|
09b326ae86 | ||
|
|
a60f667b25 | ||
|
|
6f7cf1bd31 | ||
|
|
8751bc433f | ||
|
|
afbee40bc0 | ||
|
|
81e9be64d2 | ||
|
|
0cd4972d11 | ||
|
|
938609818f | ||
|
|
487491e6f2 | ||
|
|
a6ccc7e5d2 | ||
|
|
39dd39cc09 | ||
|
|
1a017ff697 | ||
|
|
591adfcb25 |
No files matched your search
@@ -1,3 +0,0 @@
|
||||
higan/profile/WonderSwan.sys/internal.ram
|
||||
higan/profile/WonderSwan Color.sys/internal.ram
|
||||
docs_build/
|
||||
@@ -1,59 +0,0 @@
|
||||
# NOTE: This file is not part of the official higan source, it's been added
|
||||
# to help build WIP binaries with minimal fuss.
|
||||
|
||||
image: debian:stable
|
||||
|
||||
linux-x86_64-binaries:
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential libgtk2.0-dev libpulse-dev mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl1.2-dev libxv-dev libao-dev libopenal-dev libudev-dev mkdocs
|
||||
- make -C icarus compiler=g++
|
||||
- make -C higan compiler=g++
|
||||
- LC_ALL=C.UTF-8 mkdocs build
|
||||
- mkdir higan-nightly
|
||||
- cp -a icarus/out/icarus higan-nightly/icarus
|
||||
- cp -a icarus/Database higan-nightly/
|
||||
- cp -a higan/out/higan higan-nightly/higan
|
||||
- cp -a higan/systems/* higan-nightly/
|
||||
- cp -a shaders "higan-nightly/Video Shaders"
|
||||
- cp -a docs_build higan-nightly/docs
|
||||
- cp -a GPLv3.txt higan-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- higan-nightly/*
|
||||
|
||||
windows-x86_64-binaries:
|
||||
# This is a normal Windows cross-compile process, except that
|
||||
# nall::chrono tries to use clock_gettime on Windows even
|
||||
# though it's a POSIX function, and for some weird reason mingw has
|
||||
# clock_gettime() in the pthread library.
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential mingw-w64 mkdocs
|
||||
- sed -i -e 's/-lole32/& -static -lpthread/' nall/GNUmakefile
|
||||
- make -C icarus platform=windows compiler="x86_64-w64-mingw32-g++ -static-libgcc -static-libstdc++" windres="x86_64-w64-mingw32-windres"
|
||||
- make -C higan platform=windows compiler="x86_64-w64-mingw32-g++ -static-libgcc -static-libstdc++" windres="x86_64-w64-mingw32-windres"
|
||||
- LC_ALL=C.UTF-8 mkdocs build
|
||||
- mkdir higan-nightly
|
||||
- cp -a icarus/out/icarus higan-nightly/icarus.exe
|
||||
- cp -a icarus/Database higan-nightly/
|
||||
- cp -a higan/out/higan higan-nightly/higan.exe
|
||||
- cp -a higan/systems/* higan-nightly/
|
||||
- cp -a shaders "higan-nightly/Video Shaders"
|
||||
- cp -a docs_build higan-nightly/docs
|
||||
- cp -a GPLv3.txt higan-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- higan-nightly/*
|
||||
|
||||
libretro-test:
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential git
|
||||
# git refuses to even attempt a merge if you haven't told it who you are,
|
||||
# even if you use --no-commit. *sigh*
|
||||
- [email protected] git merge origin/libretro
|
||||
- make -C higan compiler=g++ binary=library target=libretro
|
||||
- mkdir libretro-nightly
|
||||
- cp -a higan/out/higan_sfc_libretro.so libretro-nightly/
|
||||
- cp -a GPLv3.txt libretro-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- libretro-nightly/*
|
||||
@@ -1,9 +0,0 @@
|
||||
Contributing to higan
|
||||
=====================
|
||||
|
||||
If you would like to propose a change to higan,
|
||||
you should create an account on the [official forums][f],
|
||||
go to the "Projects" forum and the "higan" sub-forum,
|
||||
and post your idea in a new topic there.
|
||||
|
||||
[f]: https://board.byuu.org/
|
||||
@@ -1,674 +0,0 @@
|
||||
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>.
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
----------------------------------------------------------------------
|
||||
higan - Suite of videogame console emulators
|
||||
icarus - Game library importer for higan
|
||||
|
||||
Copyright © 2004-2017 byuu
|
||||
|
||||
https://byuu.org/
|
||||
|
||||
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, specifically version 3 of the License
|
||||
and no other 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/>.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
hiro - User interface toolkit
|
||||
libco - C cooperative threading library
|
||||
nall - C++ template library
|
||||
ruby - Hardware abstraction layer
|
||||
|
||||
Copyright © 2006-2017 byuu
|
||||
|
||||
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.
|
||||
----------------------------------------------------------------------
|
||||
@@ -1,48 +0,0 @@
|
||||
The unofficial higan repository
|
||||
===============================
|
||||
|
||||
higan emulates a number of classic video-game consoles of the 1980s and 1990s,
|
||||
allowing you to play classic games on a modern general-purpose computer.
|
||||
|
||||
This repository includes
|
||||
the source-code for
|
||||
stable and WIP releases of higan,
|
||||
starting during the development of v068.
|
||||
It also includes community-maintained documentation.
|
||||
|
||||
Basically,
|
||||
apart from `.gitignore` files,
|
||||
anything in the
|
||||
[higan](higan/),
|
||||
[hiro](hiro/),
|
||||
[icarus](icarus/),
|
||||
[libco](libco/),
|
||||
[nall](nall/),
|
||||
[ruby](ruby/),
|
||||
or [shaders](shaders/)
|
||||
directories should be exactly as it appeared in official releases.
|
||||
Everything else has been added for various reasons.
|
||||
|
||||
Official higan resources
|
||||
------------------------
|
||||
|
||||
- [Official homepage](https://byuu.org/emulation/higan/)
|
||||
- [Official forum](https://board.byuu.org/viewforum.php?f=4)
|
||||
|
||||
Unofficial higan resources
|
||||
--------------------------
|
||||
|
||||
- Documentation for
|
||||
[the current stable version][stadocs]
|
||||
- [Source code repository](https://gitlab.com/higan/higan/)
|
||||
archives official higan releases
|
||||
and WIP snapshots
|
||||
since approximately v067r21
|
||||
- [Latest WIP build for Windows][wipwin]
|
||||
- Documentation for
|
||||
[the latest WIP version][wipdocs]
|
||||
|
||||
|
||||
[wipwin]: https://gitlab.com/higan/higan/-/jobs/artifacts/master/download?job=windows-x86_64-binaries
|
||||
[stadocs]: https://higan.readthedocs.io/
|
||||
[wipdocs]: https://higan.readthedocs.io/en/latest/
|
||||
-294
@@ -1,294 +0,0 @@
|
||||
|
||||
higan - "Now you're playing with fire!"
|
||||
=======================================
|
||||
|
||||
higan is a multi-system emulator that began development on October 14th, 2004.
|
||||
It currently plays games for the following systems:
|
||||
|
||||
* Nintendo Famicom (NES), Super Famicom (SNES)
|
||||
* Nintendo Game Boy, Game Boy Color + Game Boy Advance
|
||||
* Sega Master System, Game Gear + Mega Drive (Genesis)
|
||||
* NEC PC Engine (TurboGrafx) + SuperGrafx
|
||||
* Bandai WonderSwan + WonderSwan Color
|
||||
|
||||
|
||||
Supported Systems
|
||||
-----------------
|
||||
|
||||
* FreeBSD 10+
|
||||
* Windows 7, 8, 10
|
||||
* Linux 3.2+
|
||||
* OS X 10.7 Lion or above
|
||||
|
||||
You'll need a fast CPU, with clock speed being vastly more important.
|
||||
Dual-core is perfectly adequate, since higan doesn't make significant use
|
||||
of more than one core.
|
||||
|
||||
For Intel, you're looking at a 3.5 GHz Haswell architecture or better,
|
||||
whilst AMD users will want a similarly specced Ryzen build.
|
||||
|
||||
|
||||
Controller Setup
|
||||
----------------
|
||||
|
||||
First, you'll want to configure your controllers. Choose Settings -> Input and
|
||||
pick the system you'd like to configure. If you have two players, or a special
|
||||
game (eg. Mario Paint), you can pick the controller port and device here.
|
||||
|
||||
To assign inputs, double click the name and press the stick or button on your
|
||||
controller. You can have multiple assignments, for example both keyboard and
|
||||
joypad. The Erase button clears the assignments for one input; Reset clears
|
||||
them all in one go.
|
||||
|
||||
(Normally you only need to do this once. But because of how USB works,
|
||||
it's impossible to tell identical controllers apart. This means if you move
|
||||
one to another port, it counts as a new device, and you'll have to reassign
|
||||
the buttons or move it back.)
|
||||
|
||||
|
||||
Loading Games
|
||||
-------------
|
||||
|
||||
After this you can go to Library -> Load ROM File and select a game. higan
|
||||
adds it to your library, and it should start immediately. (Game Boy Advance
|
||||
titles need one more step; please see the FAQ below.)
|
||||
|
||||
To add games en masse, you can use Library -> Import ROM Files. This opens
|
||||
icarus, where you can choose a folder of ROMs, then select the ones you want
|
||||
to import.
|
||||
|
||||
In both cases, if you choose a system under the Library submenus, all games
|
||||
added will show up in a file browser under the Emulation folder in your user
|
||||
profile. The path can be changed under Settings -> Advanced if desired.
|
||||
|
||||
|
||||
Controller Ports
|
||||
----------------
|
||||
|
||||
If you're emulating a console, you need to plug the controllers in, since
|
||||
there's no connection by default.
|
||||
|
||||
Usually this means selecting eg. Super Famicom -> Controller Port 1 -> Gamepad,
|
||||
for example. However, some games require other peripherals like the SNES Mouse.
|
||||
|
||||
|
||||
Troubleshooting & FAQ
|
||||
---------------------
|
||||
|
||||
Q: What's the Library?
|
||||
|
||||
A: higan loads folders containing all the files needed to run the game.
|
||||
Odds are you have PC games and music albums organised the same way.
|
||||
This does mean that to play the games, you have to import them first.
|
||||
|
||||
If you're familiar with iTunes or Steam, you already know how this works!
|
||||
|
||||
|
||||
Q: Importing vs. loading? What's the difference?
|
||||
|
||||
A: The "Library -> Load ROM File" menu is a shortcut. It adds the game to your
|
||||
library, then opens it without the manual import process.
|
||||
|
||||
However, if you have lots of games to add at once, you'll want
|
||||
"Import ROM Files" instead.
|
||||
|
||||
|
||||
Q: Why's higan say I'm missing a file ("Game Boy Advance.sys/bios.rom")?
|
||||
|
||||
A: This is the ROM for the startup screen you see when you switch on the
|
||||
Game Boy Advance. Games require it to run, but like other ROMs, it's
|
||||
copyrighted and therefore not provided with higan.
|
||||
|
||||
Having acquired a copy, you'll have to drop it in the requested folder,
|
||||
then rename it to bios.rom.
|
||||
|
||||
|
||||
Q: Where are the games imported? Where did all my save files go?
|
||||
|
||||
A: Check the path under Settings -> Advanced. On Windows it'll probably be
|
||||
something like C:\Users\<name>\Emulation, organised by system. The saves
|
||||
are typically named save.ram.
|
||||
|
||||
|
||||
Q: Where can I find the settings?
|
||||
|
||||
A: There's a few possible locations for settings.bml.
|
||||
|
||||
1) In the same folder as the higan executable.
|
||||
|
||||
2) In the older location if you previously installed higan:
|
||||
|
||||
C:\Users\<name>\AppData\Roaming\higan (Windows)
|
||||
/home/<name>/.config/higan (BSD, Linux)
|
||||
|
||||
3) In the new location (created if the others aren't found):
|
||||
|
||||
C:\Users\<name>\AppData\Local\higan (Windows)
|
||||
/home/<name>/.local/share/higan (BSD, Linux)
|
||||
/Users/<name>/Library/Application Support/higan (Mac)
|
||||
|
||||
higan checks these in order, so you can make a portable install if you like.
|
||||
|
||||
(macOS normally hides the Library folder. To open it, switch to Finder,
|
||||
hold the Option key and select Go -> Library from the menu.)
|
||||
|
||||
|
||||
Q: I set up my gamepads, but they don't work!
|
||||
|
||||
A: Try configuring the ports found in the system menu (eg.
|
||||
Super Famicom -> Controller Port 1 -> Gamepad). Like a real console,
|
||||
fresh higan installs come without any controllers plugged in.
|
||||
|
||||
|
||||
Q: I upgraded higan, why do I get a black screen? What's "Ignore Manifests?"
|
||||
|
||||
A: higan looks at a file called "manifest.bml" to get the information needed
|
||||
to run each game. However, the format has changed over time, making older
|
||||
manifests incompatible with newer higan releases.
|
||||
|
||||
If you tick "Settings -> Advanced -> Ignore Manifests," you might find this
|
||||
resolves the problem. This can be useful for developers and testers.
|
||||
However, it breaks a few titles that require manifests to work!
|
||||
|
||||
Should you find yourself in this situation, consider removing manifest.bml.
|
||||
|
||||
(By default, no manifests are created; higan looks at the files in the
|
||||
game folder, and with the help of a database, tries to regenerate the
|
||||
correct one each time you load the game.)
|
||||
|
||||
|
||||
Q: I have "Ignore Manifests" ticked, but the game won't run?
|
||||
|
||||
A: A few games have especially quirky setups that require manifests for
|
||||
the time being, so you'll need to untick this option:
|
||||
|
||||
* Far East of Eden: Tengai Makyou Zero (English translation only)
|
||||
* Campus Challenge '92
|
||||
* PowerFest '94
|
||||
|
||||
|
||||
Q: Why's the audio lag, stutter, distort, or sound robotic?
|
||||
|
||||
A: If you have an Atom, certain Celeron models, or an older AMD processor
|
||||
(or even an especially old Intel such as a Core 2 Duo)... then these aren't
|
||||
fast enough, sorry. :(
|
||||
|
||||
Try going into the Settings -> Advanced menu, then pick a different audio
|
||||
driver and restart higan. WASAPI can be fussy on some devices.
|
||||
|
||||
Select Settings -> Audio and experiment with the latency. Larger values
|
||||
should be more reliable, with the downside of laggier game controls.
|
||||
|
||||
Occasionally software that hooks into the system or other apps, for example
|
||||
mouse settings panels, can cause lag and other problems.
|
||||
|
||||
Because higan is CPU-intensive and single-threaded, it can interact badly
|
||||
with video capture which is yet another burden on the system. If you're
|
||||
trying to stream or broadcast, and you have Windows 7, consider disabling
|
||||
DWM. Also, look up how to configure hardware encoding (eg. QuickSync).
|
||||
|
||||
|
||||
Q: Can I get smoother video?
|
||||
|
||||
A: Try Settings -> Video -> Exclusive mode, then switch to fullscreen. This
|
||||
currently requires the Direct3D video driver under Settings -> Advanced
|
||||
in order to work.
|
||||
|
||||
(Exclusive fullscreen is pretty experimental at the moment.
|
||||
There are cases where it fails badly, so save your work!)
|
||||
|
||||
Exclusive mode will normally yield what's known as "tearing." If this
|
||||
bothers you, there's an alternative... albeit one with serious gotchas,
|
||||
which is why it's hidden away.
|
||||
|
||||
Close higan, then open up settings.bml and look for the following:
|
||||
|
||||
Video
|
||||
Driver:Direct3D
|
||||
Synchronize:false
|
||||
...
|
||||
|
||||
Change false to true, save the file, then start higan and untick
|
||||
Settings -> Synchronize Audio.
|
||||
|
||||
Keep in mind that this setting can and will reduce sound quality, as GPUs
|
||||
and sound cards in modern PCs generally are not synchronised with each
|
||||
other. The second big consideration is that your refresh rate needs to
|
||||
match the game.
|
||||
|
||||
PAL and NTSC titles run at 50 Hz and 60 Hz, respectively. This applies to
|
||||
all console systems. Of the handhelds: Game Boy, Game Boy Color and
|
||||
Game Boy Advance run at 60 Hz, while WonderSwan runs at 75 Hz.
|
||||
|
||||
This means you'll need a monitor that supports these frequencies, set to
|
||||
the appropriate display mode. Not all of them do. If your refresh rate
|
||||
doesn't match, games will run at the wrong speed.
|
||||
|
||||
|
||||
Online Resources
|
||||
----------------
|
||||
|
||||
Official homepage + forum:
|
||||
|
||||
https://byuu.org/emulation/higan
|
||||
https://board.byuu.org/viewforum.php?f=4
|
||||
|
||||
Unoffical source code repository + documentation:
|
||||
|
||||
https://gitlab.com/higan/higan
|
||||
https://higan.readthedocs.io
|
||||
|
||||
Info on game folders and firmware:
|
||||
|
||||
https://byuu.org/emulation/higan/game-paks
|
||||
https://byuu.org/emulation/higan/firmware
|
||||
|
||||
Donations:
|
||||
|
||||
https://patreon.com/byuu
|
||||
|
||||
Commercial use:
|
||||
|
||||
https://byuu.org/emulation/higan/licensing
|
||||
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
Original author:
|
||||
|
||||
byuu
|
||||
|
||||
We'd like to acknowledge many invaluable contributions made to higan
|
||||
by the following individuals:
|
||||
|
||||
Andreas Naive Hendricks266 Overload
|
||||
Ange Albertini hex_usr p4plus2
|
||||
anomie jchadwick quequotion
|
||||
AWJ Jonas Quinn RedDwarf
|
||||
Bisqwit kode54 Richard Bannister
|
||||
blargg krom Ryphecha
|
||||
Łukasz Krawczyk Lioncash segher
|
||||
Cydrak Lord Nightmare Sintendo
|
||||
Danish lowkey SuperMikeMan
|
||||
DMV27 MerryMage tetsuo55
|
||||
Dr. Decapitator Matthew Callis TmEE
|
||||
endrift mightymo TRAC
|
||||
Fatbag Nach wareya
|
||||
FitzRoy ncbncb zones
|
||||
gekkio neviksti
|
||||
GIGO OV2
|
||||
|
||||
It's been a long, wild ride... apologies to anyone we've missed!
|
||||
|
||||
For more information, please see:
|
||||
|
||||
https://board.byuu.org/viewtopic.php?f=4&t=1631&p=41575#p41575
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
higan is provided under the GNU General Public License, version 3.
|
||||
However, certain libraries may be used under the more permissive ISC license.
|
||||
Please see LICENSE.txt for details.
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This uses the official W3C link-checker tool:
|
||||
#
|
||||
# https://github.com/w3c/link-checker
|
||||
#
|
||||
checklink \
|
||||
--summary \
|
||||
--broken \
|
||||
--location=http://127.0.0.1:8000/ \
|
||||
--exclude 'github.com|board.byuu.org' \
|
||||
http://127.0.0.1:8000/
|
||||
@@ -1,146 +0,0 @@
|
||||
A game folder
|
||||
is higan's way of grouping all
|
||||
the information and resources required
|
||||
to properly emulate a particular game.
|
||||
For example,
|
||||
to represent a Super Famicom game named `dkc3`,
|
||||
higan would create a game folder named `dkc3.sfc`,
|
||||
and inside it store the game data as `program.rom`
|
||||
and the save data as `save.ram`:
|
||||
|
||||
```python
|
||||
+- Super Famicom
|
||||
|
|
||||
+- dkc3.sfc
|
||||
|
|
||||
+- program.rom
|
||||
|
|
||||
+- save.ram
|
||||
```
|
||||
|
||||
In contrast,
|
||||
other emulators typically
|
||||
group resources related to a game
|
||||
by requiring that every file has the same base name
|
||||
but different file extensions.
|
||||
For example,
|
||||
if another emulator loaded the game `dkc3.sfc`
|
||||
it might store the save data in `dkc3.srm`:
|
||||
|
||||
```python
|
||||
+- Super Famicom
|
||||
|
|
||||
+- dkc3.sfc
|
||||
|
|
||||
+- dkc3.srm
|
||||
```
|
||||
|
||||
Why game folders?
|
||||
-----------------
|
||||
|
||||
A file extension doesn't offer
|
||||
much room for description,
|
||||
so the traditional name-based-grouping system
|
||||
only really works when games use a small number of files,
|
||||
Also,
|
||||
since file extensions traditionally describe
|
||||
the format of the file in question,
|
||||
it also means a game can't use
|
||||
two or more files in the same format.
|
||||
|
||||
Compared to other emulators,
|
||||
higan can use a larger number of files per game.
|
||||
For example,
|
||||
higan's low-level emulation of Super Famicom co-processors
|
||||
often requires [separate firmware files][firmware].
|
||||
higan's [MSU-1 feature][msu1]
|
||||
supports up to 99 audio tracks per game,
|
||||
and higan supports up to 133 save-states per game.
|
||||
Thus,
|
||||
higan suffers from the limitations of name-based-grouping
|
||||
more than most.
|
||||
|
||||
higan's game folders allow a game
|
||||
to have unique, descriptive filenames
|
||||
for all its resources,
|
||||
and for each file to use the extension
|
||||
that's most appropriate.
|
||||
They also allow emulator-specific extras
|
||||
like save-states and the cheat database
|
||||
to be kept separate from the game's actual data,
|
||||
by putting it in a sub-folder.
|
||||
|
||||
[msu1]: ../guides/import.md#msu-1-games
|
||||
[firmware]: ../guides/import.md#games-with-co-processor-firmware
|
||||
|
||||
For a more detailed motivation for game folders,
|
||||
see [Game Paks on the higan website][gp].
|
||||
|
||||
[gp]: https://byuu.org/emulation/higan/game-paks
|
||||
|
||||
What's in a game folder?
|
||||
------------------------
|
||||
|
||||
A game folder collects all the information relevant
|
||||
to emulating a particular game.
|
||||
Not all of the following files
|
||||
are relevant for every console,
|
||||
or even for every game on a console,
|
||||
but they may be present under particular circumstances.
|
||||
|
||||
All the files directly in the game folder
|
||||
are expected to be useful
|
||||
to all emulators that support them:
|
||||
|
||||
- `manifest.bml`:
|
||||
The [manifest](manifests.md) for this game folder.
|
||||
- `program.rom`:
|
||||
For most consoles,
|
||||
this contains
|
||||
the executable instructions and graphics data
|
||||
from the cartridge's ROM chips.
|
||||
For the Famicom,
|
||||
this contains only the executable instructions.
|
||||
- `character.rom`:
|
||||
For the Famicom,
|
||||
this contains only the graphics data
|
||||
from the cartridge's ROM chips.
|
||||
- `ines.rom`:
|
||||
While other consoles typically include enough hints
|
||||
in `program.rom` for icarus to guess a working manifest,
|
||||
the Famicom does not.
|
||||
Famicom games not stored in game folders
|
||||
typically include an "iNES header" to store that information,
|
||||
which icarus preserves after import as `ines.rom`.
|
||||
- `save.ram`:
|
||||
Games that include a save feature
|
||||
will create this file.
|
||||
Note that it is only written to disk
|
||||
when higan exits gracefully;
|
||||
if higan crashes or is forced to quit,
|
||||
in-game saves may be lost.
|
||||
Other emulators sometimes call this an "SRAM file",
|
||||
even though the same filename is used
|
||||
with games that included EEPROM or Flash storage,
|
||||
not just battery-backed Static RAM.
|
||||
- `rtc.ram`:
|
||||
Games that include a calendar or real-time clock
|
||||
will create this file.
|
||||
- `*.data.rom`, `*.program.rom`:
|
||||
Files named like this are usually [co-processor firmware][firmware].
|
||||
- `msu1.rom`:
|
||||
Holds streamable data for [the MSU-1][msu1].
|
||||
- `track-*.pcm`:
|
||||
Holds streamable audio for [the MSU-1][msu1].
|
||||
|
||||
Files that are only useful to higan specifically
|
||||
are placed in a `higan` sub-folder:
|
||||
|
||||
- `cheats.bml`:
|
||||
All information present in
|
||||
[the Cheat Editor](../interface/higan-tools.md#the-cheat-editor)
|
||||
is stored here.
|
||||
- `states/quick/slot-*.bst`:
|
||||
All [Quick States](save-states.md#quick-states) are stored here.
|
||||
- `states/managed/slot-*.bst`:
|
||||
All [Manager States](save-states.md#manager-states) are stored here.
|
||||
@@ -1,87 +0,0 @@
|
||||
The game library
|
||||
is the folder where all the
|
||||
[game folders](game-folders.md) go.
|
||||
When [icarus](../interface/icarus.md) imports a game,
|
||||
it creates or updates
|
||||
the corresponding game folder in the game library.
|
||||
When you use the console sub-menu items
|
||||
in [higan's Library menu](../interface/higan.md#the-library-menu),
|
||||
higan shows you the games for that console
|
||||
that are already in the library.
|
||||
|
||||
Inside the game library folder
|
||||
there is a sub-folder for each system.
|
||||
Inside each system folder
|
||||
are the game folders
|
||||
for each imported game.
|
||||
|
||||
Why a game library?
|
||||
-------------------
|
||||
|
||||
higan [requires game folders](game-folders.md#why-game-folders),
|
||||
but it also invented game folders,
|
||||
so existing games need
|
||||
to be converted to game folder format
|
||||
before higan can play them.
|
||||
The converted copy of a game needs to go *somewhere*.
|
||||
|
||||
Also,
|
||||
for people who have a huge collection of games,
|
||||
the game library only lists the games they actually play,
|
||||
rather than every possible game,
|
||||
making it easier to find the games they like.
|
||||
|
||||
|
||||
Where is the game library?
|
||||
--------------------------
|
||||
|
||||
In Windows,
|
||||
the default location of
|
||||
the game library is the `Emulation` folder
|
||||
inside your profile folder
|
||||
(To find your profile folder,
|
||||
press `Win+R` to open the Run dialog,
|
||||
then type `%USERPROFILE%` and press Enter).
|
||||
|
||||
In Linux,
|
||||
the default location of
|
||||
the game library is the `Emulation` directory
|
||||
inside your home directory.
|
||||
|
||||
On all platforms,
|
||||
the game library location can be configured.
|
||||
See [Moving the Game Library](#moving-the-game-library)
|
||||
below.
|
||||
|
||||
Moving the game library
|
||||
-----------------------
|
||||
|
||||
Moving the game library is a little complicated,
|
||||
because there are two parts to it:
|
||||
telling icarus where to put imported games,
|
||||
and telling higan where to find them.
|
||||
|
||||
1. Move your existing game library folder
|
||||
to the new location,
|
||||
or otherwise create the folder you want higan to use
|
||||
as its game library.
|
||||
1. Launch icarus,
|
||||
then click the "Settings ..." button in the lower-right,
|
||||
to open
|
||||
[the Settings dialog][icsettings].
|
||||
1. Click the "Change ..." button on the right.
|
||||
A [filesystem browser][fsbrowser] window will appear,
|
||||
allowing you to choose
|
||||
where imported games will be stored.
|
||||
1. Launch higan,
|
||||
then from [the Settings menu][settingsmenu],
|
||||
choose "Advanced ..."
|
||||
to open [higan's Advanced settings][advsettings].
|
||||
1. Click the "Change ..." button on the right.
|
||||
A [filesystem browser][fsbrowser] will appear,
|
||||
allowing you to choose the same folder again.
|
||||
|
||||
[icsettings]: ../interface/icarus.md#the-icarus-settings-dialog
|
||||
[fsbrowser]: ../interface/common.md#the-filesystem-browser
|
||||
[settingsmenu]: ../interface/higan.md#the-settings-menu
|
||||
[advsettings]: ../interface/higan-settings.md#advanced
|
||||
@@ -1,142 +0,0 @@
|
||||
If a [game folder](game-folders.md) is
|
||||
the emulation equivalent of
|
||||
a physical game cartridge,
|
||||
a manifest is like
|
||||
the circuit board inside the cartridge:
|
||||
it connects all the other parts together
|
||||
and to the console itself.
|
||||
|
||||
If you load a game into higan,
|
||||
you can look at the game's manifest
|
||||
by opening [the Tools menu](../interface/higan.md#the-tools-menu)
|
||||
and choosing [Manifest Viewer](../interface/higan-tools.md#the-manifest-viewer).
|
||||
|
||||
Why manifests?
|
||||
--------------
|
||||
|
||||
For most consoles,
|
||||
a manifest isn't strictly necessary:
|
||||
the raw game data provides enough clues
|
||||
for emulators to guess the circuit board configuration,
|
||||
or at least
|
||||
to guess a *reasonable* configuration.
|
||||
However,
|
||||
relying on such heuristics often leads to problems:
|
||||
|
||||
- Inventing heuristics
|
||||
that correctly guess the configuration
|
||||
for a particular game is very difficult.
|
||||
- If you change the rules,
|
||||
you really need to re-test
|
||||
the console's entire game library
|
||||
to check you haven't broken anything.
|
||||
- Some games accidentally do the wrong thing.
|
||||
- The Mega Drive game *Warrior of Rome II*
|
||||
follows the rule for a European game running at 50fps,
|
||||
when it's really an American game designed for 60fps.
|
||||
- Some games deliberately do the wrong thing.
|
||||
- The Game Boy Advance game *Top Gun - Combat Zones*
|
||||
follows the rule for
|
||||
(several kinds of)
|
||||
save-game storage.
|
||||
If it detects any save-game storage,
|
||||
it assumes it's running in an emulator
|
||||
and disables all the main menu options.
|
||||
- Heuristics are difficult to reason about.
|
||||
- If a homebrew developer
|
||||
takes a game that almost does what they want
|
||||
and changes it slightly,
|
||||
it may cause the emulator
|
||||
to do something completely different
|
||||
because the modified game
|
||||
is now a closer match to some other game.
|
||||
- Heuristics can make sensible configurations impossible.
|
||||
- If a homebrew developer wants
|
||||
a particular hardware configuration
|
||||
that no official games happened to use,
|
||||
it may not be possible
|
||||
to trigger it heuristically.
|
||||
- It's not hardware accurate.
|
||||
- The original console did not use heuristics
|
||||
to guess a hardware configuration,
|
||||
it just used the actual configuration
|
||||
of the actual hardware.
|
||||
|
||||
Manifests provide a way to describe
|
||||
*exactly* what hardware configuration to use,
|
||||
no guessing required,
|
||||
avoiding all these problems entirely.
|
||||
|
||||
Where do manifests come from?
|
||||
-----------------------------
|
||||
|
||||
Ideally,
|
||||
everybody who extracts the data from a game cartridge would
|
||||
also record the board configuration in a manifest file.
|
||||
Unfortunately,
|
||||
manifests were invented long after cartridge extracting,
|
||||
so there are a lot of previously extracted games
|
||||
with no manifest attached.
|
||||
|
||||
If a game doesn't come with a manifest,
|
||||
it may be possible to look up the correct manifest.
|
||||
The
|
||||
[SNES Preservation Project](https://preservation.byuu.org/)
|
||||
intends to re-dump every Super Famicom game
|
||||
and record its board configuration at the same time.
|
||||
Given a game's data,
|
||||
you can take the SHA256 fingerprint
|
||||
and look it up in the Project's database
|
||||
to find the correct manifest to use.
|
||||
|
||||
Unfortunately,
|
||||
this doesn't help for any other console.
|
||||
Even for the Super Famicom,
|
||||
there will always be games
|
||||
too rare to acquire for re-dumping,
|
||||
and homebrew that was never on a physical cartridge to begin with.
|
||||
For these games,
|
||||
heuristics will always be needed as a fallback,
|
||||
but at least if the heuristics are wrong
|
||||
they can be overridden.
|
||||
|
||||
Ignoring manifests
|
||||
------------------
|
||||
|
||||
Occasionally,
|
||||
a newly-dumped game will turn out to have
|
||||
a configuration that can't be expressed
|
||||
in the existing manifest file format,
|
||||
and a new format must be designed.
|
||||
If manifests were always written inside game folders,
|
||||
games with old-format manifests
|
||||
would break when played in emulators that supported the new format.
|
||||
|
||||
Therefore,
|
||||
icarus [defaults](../interface/icarus.md#the-icarus-settings-dialog)
|
||||
to not writing out manifests when it imports games,
|
||||
and higan [defaults](../interface/higan-settings.md#advanced)
|
||||
to ignoring manifests that are present.
|
||||
Instead,
|
||||
when higan loads a game,
|
||||
it will ask icarus to generate a temporary manifest in the latest format,
|
||||
based on the files present in the game folder
|
||||
and how they are likely to go together.
|
||||
|
||||
If the manifest that icarus generates
|
||||
for some particular game
|
||||
is broken or buggy,
|
||||
you'll need to switch the manifest machinery back on
|
||||
so you can fix it:
|
||||
|
||||
- Turn on "Create manifests" in icarus' settings
|
||||
- Re-import the game in question
|
||||
to store the broken manifest
|
||||
as `manifest.bml` in the game folder.
|
||||
- Edit `manifest.bml` as you see fit
|
||||
- Turn off "Ignore manifests" in higan's settings
|
||||
- Load the game in higan to check that your changes fixed the problem
|
||||
|
||||
If the manifest format changes in the future,
|
||||
you'll have to repeat this process
|
||||
to create a fixed manifest in the new format.
|
||||
@@ -1,129 +0,0 @@
|
||||
A real game console
|
||||
is a complex piece of hardware,
|
||||
with electricity flowing through it
|
||||
in complex patterns that can't easily
|
||||
be paused or recorded.
|
||||
However,
|
||||
an emulated console is pure software:
|
||||
it only changes when the emulation software
|
||||
deliberately updates it,
|
||||
so the emulator software can save
|
||||
the entire state of the emulated console
|
||||
to disk,
|
||||
and weeks or months later
|
||||
bring it back to life
|
||||
as though nothing had ever happened.
|
||||
|
||||
Save states versus in-game saves
|
||||
--------------------------------
|
||||
|
||||
Some games include their own systems
|
||||
for saving and restoring the player's progress.
|
||||
Here are some of the differences
|
||||
between save states and in-game saves:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Save states</th>
|
||||
<th>In-game saves</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Work exactly the same way
|
||||
in every game
|
||||
</td>
|
||||
<td>
|
||||
Works differently in different games,
|
||||
some games don't support it at all
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Save at any time,
|
||||
anywhere in the game
|
||||
</td>
|
||||
<td>
|
||||
Save only at special save-points
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Loading a save
|
||||
puts the game back exactly how it was
|
||||
when you pressed save
|
||||
</td>
|
||||
<td>
|
||||
Loading a save
|
||||
restores some things
|
||||
(like the player's inventory
|
||||
or remaining lives),
|
||||
but may
|
||||
forget others
|
||||
(like taking you back to the beginning of the level)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
You can have dozens of save states
|
||||
</td>
|
||||
<td>
|
||||
Most games limit you to about 3 saves
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Can only be loaded
|
||||
by the same version of the same emulator
|
||||
that created it
|
||||
</td>
|
||||
<td>
|
||||
Works with any version of any emulator,
|
||||
can sometimes even be copied to or from physical cartridges
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**Note:**
|
||||
Loading a save state
|
||||
will reset the entire emulated console
|
||||
to the way it was when the save state was created,
|
||||
*including in-game saves*.
|
||||
If you create a save state,
|
||||
then make an in-game save,
|
||||
then load the save state,
|
||||
**your in-game save will be lost**.
|
||||
Don't do that.
|
||||
|
||||
Quick states
|
||||
------------
|
||||
|
||||
higan has five Quick State slots,
|
||||
which can be used from
|
||||
[the Tools menu](../interface/higan.md#the-tools-menu),
|
||||
or with the appropriate
|
||||
[hotkeys](../interface/higan-settings.md#hotkeys).
|
||||
|
||||
Quick states are useful
|
||||
as extra checkpoints
|
||||
in games whose checkpoints are too far apart,
|
||||
or in games that don't have checkpoints at all.
|
||||
Map the "Save Quick State" and "Load Quick State" hotkeys
|
||||
to your controller,
|
||||
and you can cheese your way through just about anything.
|
||||
|
||||
Manager states
|
||||
--------------
|
||||
|
||||
higan's
|
||||
[State Manager](../interface/higan-tools.md#the-state-manager)
|
||||
allows you to create over a hundred save states,
|
||||
and add a helpful description to each one.
|
||||
|
||||
Manager States are more cumbersome to use than Quick States,
|
||||
since they cannot be accessed with hotkeys,
|
||||
but are useful when you want quick access
|
||||
to many different parts of a game.
|
||||
@@ -1,62 +0,0 @@
|
||||
higan's original author:
|
||||
|
||||
- byuu
|
||||
|
||||
We'd like to acknowledge
|
||||
many invaluable contributions made to higan
|
||||
by the following individuals:
|
||||
|
||||
- Andreas Naive
|
||||
- Ange Albertini
|
||||
- anomie
|
||||
- AWJ
|
||||
- Bisqwit
|
||||
- blargg
|
||||
- Łukasz Krawczyk
|
||||
- Cydrak
|
||||
- Danish
|
||||
- DMV27
|
||||
- Dr. Decapitator
|
||||
- endrift
|
||||
- Fatbag
|
||||
- FitzRoy
|
||||
- gekkio
|
||||
- GIGO
|
||||
- Hendricks266
|
||||
- hex_usr
|
||||
- jchadwick
|
||||
- Jonas Quinn
|
||||
- kode54
|
||||
- krom
|
||||
- Lioncash
|
||||
- Lord Nightmare
|
||||
- lowkey
|
||||
- MerryMage
|
||||
- Matthew Callis
|
||||
- mightymo
|
||||
- Nach
|
||||
- ncbncb
|
||||
- neviksti
|
||||
- OV2
|
||||
- Overload
|
||||
- p4plus2
|
||||
- quequotion
|
||||
- RedDwarf
|
||||
- Richard Bannister
|
||||
- Ryphecha
|
||||
- segher
|
||||
- Sintendo
|
||||
- SuperMikeMan
|
||||
- tetsuo55
|
||||
- TmEE
|
||||
- TRAC
|
||||
- wareya
|
||||
- zones
|
||||
|
||||
It's been a long, wild ride...
|
||||
apologies to anyone we've missed!
|
||||
|
||||
For more information,
|
||||
see the [credits thread](
|
||||
https://board.byuu.org/viewtopic.php?f=4&t=1631&p=41575#p41575)
|
||||
on the official forums.
|
||||
-283
@@ -1,283 +0,0 @@
|
||||
I see "tearing" when a game scrolls. How can I enable vsync?
|
||||
------------------------------------------------------------
|
||||
|
||||
higan supports synchronising video output
|
||||
to the display's vertical-synchronisation (or "vsync") signal,
|
||||
but the option is hidden
|
||||
because it often causes more problems than it solves
|
||||
(see the next question).
|
||||
To enable video synchronisation:
|
||||
|
||||
- Open higan's configuration file, `settings.bml`
|
||||
- On Windows, look in `%LOCALAPPDATA%\higan`
|
||||
or beside `higan.exe`
|
||||
- On Linux, look in `~/.local/share/higan`
|
||||
- Open it in your favourite text editor
|
||||
(Windows Notepad will corrupt the file,
|
||||
use WordPad if you don't have anything better)
|
||||
- Find the line that says "Video"
|
||||
- Somewhere below that, find an indented line
|
||||
that says "Synchronize:false".
|
||||
- Change "false" to "true"
|
||||
- Save your changes to `settings.bml`
|
||||
and restart higan
|
||||
|
||||
Why is video synchronisation a problem for higan?
|
||||
-------------------------------------------------
|
||||
|
||||
**The short version:**
|
||||
Turning on video synchronisation
|
||||
cleans up video tearing,
|
||||
turning on audio synchronisation
|
||||
cleans up audio glitches,
|
||||
but turning on both
|
||||
makes audio glitches worse.
|
||||
|
||||
**The long version:**
|
||||
Enabling video synchronisation
|
||||
locks the frame-rate of the emulated console
|
||||
to the frame-rate of your computer's display.
|
||||
If your display's refresh rate exactly matches
|
||||
the emulated console's,
|
||||
games play at the correct speed
|
||||
and everything's fine.
|
||||
|
||||
However,
|
||||
modern 60Hz displays do not always match
|
||||
the emulated console's refresh rate:
|
||||
|
||||
- The Super Famicom usually runs a little faster than 60Hz
|
||||
- the PAL variants of most consoles run at 50Hz
|
||||
- the WonderSwan runs at 75Hz
|
||||
- While the Game Boy does run its LCD at 60Hz,
|
||||
games can turn the LCD off and on at any time,
|
||||
requiring emulation to pause
|
||||
until it can get back in sync
|
||||
with the computer display.
|
||||
|
||||
Because of these frame-rate differences,
|
||||
enabling video synchronisation
|
||||
can force games to run
|
||||
faster or slower than intended.
|
||||
|
||||
The consoles that higan emulates
|
||||
produce video frames and audio samples at a particular rate.
|
||||
If video synchronisation causes
|
||||
the emulated console to run, say, 5% faster than intended,
|
||||
that means audio samples are also being produced 5% faster.
|
||||
You might not notice the changed game speed,
|
||||
but you'll almost certainly notice
|
||||
the game's audio glitching constantly
|
||||
as your sound card tries to keep up.
|
||||
|
||||
Enabling
|
||||
[audio synchronisation](interface/higan.md#the-settings-menu)
|
||||
normally fixes this kind of audio glitching,
|
||||
but with video synchronisation it makes things worse:
|
||||
audio is likely to glitch
|
||||
while higan waits for a video frame to be shown,
|
||||
and video is likely to stutter
|
||||
while higan waits for an audio buffer to complete.
|
||||
|
||||
Games run too fast
|
||||
------------------
|
||||
|
||||
higan runs as fast as it can,
|
||||
but it will pause and wait
|
||||
for the audio and video drivers to catch up
|
||||
if [audio synchronisation](interface/higan.md#the-settings-menu)
|
||||
and [video synchronisation][vsync]
|
||||
are enabled, respectively.
|
||||
If games are running way too fast, here are some things to check:
|
||||
|
||||
- Make sure "Synchronize Audio" is ticked in
|
||||
[the Settings menu](interface/higan.md#the-settings-menu)
|
||||
- Make sure the Audio driver is not set to "None"
|
||||
in [the Advanced settings](interface/higan-settings.md#advanced)
|
||||
(remember to restart higan if you change driver settings)
|
||||
- Make sure your computer has speakers or headphones connected
|
||||
(some computers disable all audio if no outputs are available)
|
||||
- If you want the game to be silent,
|
||||
tick "Mute Audio" in
|
||||
[the Settings menu](interface/higan.md#the-settings-menu)
|
||||
- If you can't connect speakers or headphones to your computer,
|
||||
or you did but it didn't help,
|
||||
try enabling the secret [video synchronisation][vsync] option.
|
||||
|
||||
[vsync]: #i-see-tearing-when-a-game-scrolls-how-can-i-enable-vsync
|
||||
|
||||
Games run too slow
|
||||
------------------
|
||||
|
||||
Of all the consoles higan can emulate,
|
||||
higan's Super Famicom emulation
|
||||
is the most resource intensive.
|
||||
Full-speed emulation for the Super Famicom base unit
|
||||
requires an Intel Core 2 Duo (or AMD equivalent),
|
||||
full-speed for games with the SuperFX chip
|
||||
requires an Intel Ivy Bridge (or equivalent),
|
||||
full-speed for the wire-frame animations in *Mega Man X2*
|
||||
requires an even faster computer.
|
||||
Low-power CPUs like ARM chips,
|
||||
or Intel Atom and Celeron CPUs
|
||||
generally aren't fast enough to emulate the Super Famicom with higan,
|
||||
although other emulated consoles may work.
|
||||
|
||||
If your computer meets the general speed requirements
|
||||
but games run too slowly,
|
||||
try choosing a different
|
||||
[audio driver](interface/higan-settings.md#advanced),
|
||||
since that's usually what drives higan's timing.
|
||||
|
||||
On some computers,
|
||||
the operating system's power-management system
|
||||
may be confused by higan's unusual pattern of CPU usage
|
||||
(it runs furiously to calculate the next video frame
|
||||
and the next few milliseconds of audio,
|
||||
then stops dead as it waits for output to complete).
|
||||
If holding down
|
||||
the [fast forward hotkey](interface/higan-settings.md#hotkeys)
|
||||
runs too fast but the game normally runs too slow,
|
||||
try disabling "power saver" mode
|
||||
or enabling "performance" mode.
|
||||
|
||||
On Linux specifically,
|
||||
if your desktop environment doesn't provide a way
|
||||
to change the CPU usage governor,
|
||||
you can temporarily force your CPUs to run at full-speed
|
||||
by running the following command in a terminal:
|
||||
|
||||
echo "performance" | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
|
||||
|
||||
To put things back to normal,
|
||||
reboot the computer, or run this command instead:
|
||||
|
||||
echo "ondemand" | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
|
||||
|
||||
Why is higan so much slower than other emulators?
|
||||
-------------------------------------------------
|
||||
|
||||
The consoles higan emulates
|
||||
generally contain different devices
|
||||
(say, a CPU, an audio chip and a video chip)
|
||||
that do their own thing,
|
||||
but can interrupt each other at any time.
|
||||
Games can and do depend on timing details like
|
||||
"when the audio chip is done playing this sound,
|
||||
it will interrupt the CPU at exactly the right time
|
||||
for the CPU to fiddle with the video chip".
|
||||
higan is therefore very cautious about timing:
|
||||
while it's emulating the CPU (for example),
|
||||
at every point the emulated audio chip *might* interrupt
|
||||
the emulated CPU,
|
||||
higan switches to emulating the audio chip up to the same point
|
||||
to find out whether it *will* interrupt the CPU.
|
||||
|
||||
In this way,
|
||||
higan is a little bit like
|
||||
an office-worker trying to do the jobs of three other people
|
||||
by running from desk to desk,
|
||||
sending the same emails
|
||||
that those three people would send to each other,
|
||||
leaving itself a note at each desk to remind it
|
||||
where it was up to when it comes back.
|
||||
Although this constant switching
|
||||
is slow and inefficient,
|
||||
higan does it
|
||||
to ensure the emulated console
|
||||
always matches the behaviour
|
||||
of the original.
|
||||
|
||||
Other emulators,
|
||||
especially ones that are popular on phones and tablets,
|
||||
typically work differently.
|
||||
Although emulated devices *can* interrupt each other at any time,
|
||||
usually they don't,
|
||||
and often the only exceptions are a handful of unpopular games.
|
||||
So it's pretty safe
|
||||
(and much, much more efficient)
|
||||
to let certain devices run "ahead" of the others.
|
||||
For example,
|
||||
letting the video chip render an entire scanline at a time
|
||||
instead of working pixel-by-pixel.
|
||||
The downside is that some games don't work properly
|
||||
in these emulators,
|
||||
with game-breaking bugs,
|
||||
minor visual glitches,
|
||||
or anything in between.
|
||||
|
||||
Some emulators try to recognise
|
||||
when the user loads a specific problematic game,
|
||||
and will adjust the shortcuts the emulator takes
|
||||
so that the game runs correctly.
|
||||
This is a good way to increase game compatibility
|
||||
without losing efficiency,
|
||||
but it means the emulator does not accurately reflect
|
||||
how the original console worked,
|
||||
and it requires the emulator authors to diagnose and work around
|
||||
each individual supported game,
|
||||
instead of having one emulator that works for everything.
|
||||
|
||||
Why can't higan use multiple CPU cores?
|
||||
---------------------------------------
|
||||
|
||||
These days,
|
||||
most computers contain multiple CPU cores,
|
||||
allowing them to run different programs
|
||||
(or different parts of the same program)
|
||||
at the same time.
|
||||
Since higan requires high CPU performance,
|
||||
sometimes people suggest that it should split its work
|
||||
into multiple threads
|
||||
so it can run across multiple cores
|
||||
instead of stressing one core while the others lie idle.
|
||||
After all,
|
||||
the original consoles higan emulates
|
||||
had separate devices running independently,
|
||||
so why not run each emulated device in a separate thread?
|
||||
|
||||
The big problem with this approach is timing.
|
||||
The devices in a physical Super Famicom (for example)
|
||||
run at whatever speed they run at,
|
||||
and talk to each other whenever they feel like.
|
||||
Because every Super Famicom uses the exact same components,
|
||||
every Super Famicom runs compatible games at the exact same speed,
|
||||
and games can blindly assume that
|
||||
when operation X is complete on device A,
|
||||
device B will have finished operation Y
|
||||
and be ready to do something new.
|
||||
Meanwhile, higan's emulated components
|
||||
take an unpredictable amount of time to do their work,
|
||||
so without deliberate synchronisation
|
||||
things would break almost immediately.
|
||||
|
||||
It's not practical to make higan's emulated devices
|
||||
do their work in exactly the same amount of time
|
||||
as their hardware counterparts.
|
||||
The problem is forty years of technology
|
||||
designed to make programs run as fast as possible:
|
||||
optimising compilers
|
||||
and super-scalar, out-of-order CPU architectures
|
||||
change programs to make them faster,
|
||||
speeding up some programs more than others
|
||||
in ways that are very difficult to understand and predict.
|
||||
Even if higan's emulated devices
|
||||
ran at the exact, correct speed
|
||||
on one particular computer,
|
||||
they'd still run differently on
|
||||
a computer with a smarter CPU,
|
||||
or when compiled with a smarter compiler.
|
||||
|
||||
Since higan needs its emulated components
|
||||
to run at particular speeds,
|
||||
and it's not practical
|
||||
to make them run at those speeds naturally,
|
||||
it must force them manually.
|
||||
An emulated device runs for a little while,
|
||||
then all the others are run until they catch up.
|
||||
It's this careful management,
|
||||
regular stopping and starting,
|
||||
that makes higan slow,
|
||||
not the actual emulation of each device.
|
||||
Having multiple CPU cores waiting on each other
|
||||
would not help them wait any faster.
|
||||
@@ -1,143 +0,0 @@
|
||||
Unfortunately,
|
||||
there's no cross-platform standard for
|
||||
displaying video,
|
||||
playing audio,
|
||||
and accepting input from game controllers.
|
||||
Or rather,
|
||||
there are many standards,
|
||||
and different ones work best
|
||||
on different computers.
|
||||
Therefore,
|
||||
higan comes with "drivers"
|
||||
for video, audio and input,
|
||||
so you can find the one that works best for you.
|
||||
To see what drivers you're currently using,
|
||||
or to choose different ones,
|
||||
go to
|
||||
[the Advanced tab](../interface/higan-settings.md#advanced)
|
||||
of the Settings window.
|
||||
|
||||
Here are the most notable drivers
|
||||
for each platform
|
||||
in each category.
|
||||
If your copy of higan
|
||||
includes a driver not listed here,
|
||||
it's probably a reasonable choice,
|
||||
so try it out and see how you like it.
|
||||
|
||||
**Note:** After changing any driver,
|
||||
you must restart higan for the change to take effect.
|
||||
|
||||
**Note:**
|
||||
Video, Audio and Input
|
||||
all have a special driver named "None".
|
||||
This is a dummy driver that does nothing
|
||||
(draws no video,
|
||||
plays no audio,
|
||||
accepts no input),
|
||||
so don't choose it unless you're
|
||||
trying to diagnose problems with other drivers.
|
||||
|
||||
Video
|
||||
-----
|
||||
|
||||
On Windows:
|
||||
|
||||
- **OpenGL** is the best choice,
|
||||
since it's fast
|
||||
and it supports [custom shaders](shaders.md),
|
||||
but requires support for OpenGL 3.2.
|
||||
This excludes some older and weaker hardware.
|
||||
- **Direct3D** is a good choice
|
||||
if OpenGL is unavailable.
|
||||
It also allows
|
||||
[Exclusive fullscreen](../interface/higan-settings.md#video),
|
||||
bypassing Windows' desktop compositor.
|
||||
- **GDI** is the safest choice,
|
||||
but performs very poorly at large sizes.
|
||||
|
||||
On Linux:
|
||||
|
||||
- **OpenGL** is the best choice,
|
||||
since it's fast
|
||||
and it supports [custom shaders](shaders.md),
|
||||
but requires support for OpenGL 3.2.
|
||||
You can check what version of OpenGL
|
||||
your system supports by running
|
||||
`glxinfo | grep 'core profile version'`
|
||||
in a terminal.
|
||||
- **XVideo** is also fast,
|
||||
but may be low-quality,
|
||||
and generally only supports the "Blur" shader,
|
||||
not "None".
|
||||
- **XShm** is the safest choice,
|
||||
but performs very poorly at large sizes.
|
||||
|
||||
Audio
|
||||
-----
|
||||
|
||||
On Windows:
|
||||
|
||||
- **ASIO** offers the lowest possible latency,
|
||||
but is the least likely to work on any given computer.
|
||||
- **WASAPI** offers low latency,
|
||||
but is only slightly more likely to work than ASIO.
|
||||
It also offers
|
||||
[Exclusive Mode](../interface/higan-settings.md#audio),
|
||||
which can improve audio quality and lower latency,
|
||||
but may be better or worse than shared mode
|
||||
in practice.
|
||||
- **XAudio2** is a good choice,
|
||||
but it requires the latest (June 2010) version
|
||||
of the [DirectX 9 End-User Runtime][dx9]
|
||||
to be installed.
|
||||
- **DirectSound** is the safest choice,
|
||||
and not a bad one.
|
||||
|
||||
[dx9]: https://www.microsoft.com/en-us/download/details.aspx?id=35
|
||||
|
||||
On Linux:
|
||||
|
||||
- **PulseAudio** or **PulseAudioSimple**
|
||||
are almost certainly the drivers to use,
|
||||
since almost every Linux distribution uses
|
||||
[PulseAudio](https://en.wikipedia.org/wiki/PulseAudio)
|
||||
to manage audio output.
|
||||
The two drivers should behave identically,
|
||||
but some users report one working better than the other.
|
||||
- **ALSA** is a good choice
|
||||
for Linux distributions that do not use PulseAudio.
|
||||
If PulseAudio is running,
|
||||
this will wind up using PulseAudio's ALSA emulation,
|
||||
or failing due to "sound card already in use",
|
||||
unless you use a tool like `pasuspender`.
|
||||
- **OSS** may be useful
|
||||
for non-Linux platforms,
|
||||
or for Linux systems with the third-party OSSv4
|
||||
kernel drivers installed.
|
||||
For most Linux systems,
|
||||
this will use ALSA's OSS emulation,
|
||||
PulseAudio's OSS emulation,
|
||||
or not work at all.
|
||||
|
||||
**Note:**
|
||||
By default,
|
||||
higan uses the audio driver to regulate its emulation speed.
|
||||
If games run too fast on your computer,
|
||||
see [Games run too fast](../faq.md#games-run-too-fast) in the FAQ.
|
||||
|
||||
Input
|
||||
-----
|
||||
|
||||
On Windows,
|
||||
"Windows" is the only driver available,
|
||||
and uses RawInput for keyboard and mouse input,
|
||||
XInput for Xbox controllers,
|
||||
and DirectInput for other controllers.
|
||||
|
||||
On Linux:
|
||||
|
||||
- **udev** supports every input device,
|
||||
but requires a modern Linux system.
|
||||
- **Xlib** is the safest choice,
|
||||
but only supports keyboard and mouse input.
|
||||
@@ -1,730 +0,0 @@
|
||||
Before it can load a game,
|
||||
higan requires that all the game's data
|
||||
be stored correctly in
|
||||
[the Game Library](../concepts/game-library.md).
|
||||
For [regular games](#regular-games)
|
||||
this is simple,
|
||||
but some games require special treatment,
|
||||
especially those that make use of
|
||||
unusual hardware.
|
||||
|
||||
Regular games
|
||||
-------------
|
||||
|
||||
higan's importing tool, icarus, can import games
|
||||
in the most commonly-used formats
|
||||
for each supported console,
|
||||
and also those same formats inside `.zip` files
|
||||
(as long as the `.zip` file contains only one game).
|
||||
More advanced compression formats
|
||||
like RAR or 7-zip are not supported.
|
||||
|
||||
To import a game,
|
||||
open [the Library menu](../interface/higan.md#the-library-menu),
|
||||
choose "Load ROM File ..."
|
||||
to open [a filesystem browser](../interface/common.md#the-filesystem-browser),
|
||||
choose the ROM file of the game you want to play,
|
||||
and it will be imported into the library and start playing.
|
||||
|
||||
**Note:**
|
||||
If you want to import many games,
|
||||
run icarus directly,
|
||||
or choose "Import ROM Files ..."
|
||||
from the Library menu
|
||||
(which just runs icarus anyway).
|
||||
See [the icarus documentation](../interface/icarus.md) for details.
|
||||
|
||||
To play a game for a particular console from your library,
|
||||
open the Library menu,
|
||||
pick the console manufacturer sub-menu
|
||||
(Nintendo for the Super Famicom,
|
||||
Bandai for the WonderSwan,
|
||||
etc.)
|
||||
then choose the appropriate console menu item.
|
||||
A filesystem browser will appear
|
||||
listing all the games in the library
|
||||
for that particular console.
|
||||
Select the game you want to play
|
||||
and click the Open button,
|
||||
or just double-click the game,
|
||||
and it will begin playing.
|
||||
|
||||
**Note:**
|
||||
Sometimes
|
||||
the NTSC and PAL variants of a particular console
|
||||
behave differently,
|
||||
or the Japanese and American variants.
|
||||
When choosing a game from the Game Library,
|
||||
a drop-down list in the bottom-right of the filesystem browser
|
||||
allows you to choose which regional variant
|
||||
of the console higan should emulate.
|
||||
For most consoles,
|
||||
higan can reliably guess which variant to use,
|
||||
and the list defaults to "Auto".
|
||||
|
||||
Games with co-processor firmware
|
||||
--------------------------------
|
||||
|
||||
Many games included extra chips inside the game cartridge,
|
||||
to provide enhanced capabilities of one kind or another.
|
||||
Sometimes,
|
||||
those extra chips were separate CPUs
|
||||
running their own separate firmware,
|
||||
and for those cases
|
||||
higan requires a copy of the co-processor firmware
|
||||
as well as the actual game data.
|
||||
Unfortunately,
|
||||
like games themselves,
|
||||
co-processor firmware cannot legally be distributed,
|
||||
so you'll need to obtain
|
||||
copies of the relevant firmware data
|
||||
yourself.
|
||||
|
||||
To import a game that requires co-processor firmware,
|
||||
you must first combine the game data and the firmware into a single file.
|
||||
For example,
|
||||
let's say you want to import *Super Bases Loaded 2* for the Super Famicom,
|
||||
which is stored in the file `sbl2.sfc`
|
||||
and requires firmware for the DSP1 co-processor
|
||||
stored in `dsp1.program.rom` and `dsp1.data.rom`.
|
||||
On Windows,
|
||||
you can combine them from the command-line like this:
|
||||
|
||||
```dos
|
||||
copy /b sbl2.sfc + dsp1.program.rom + dsp1.data.rom
|
||||
```
|
||||
|
||||
On Linux,
|
||||
the equivalent command-line syntax is:
|
||||
|
||||
```bash
|
||||
cat dsp1.program.rom dsp1.data.rom >> sbl2.sfc
|
||||
```
|
||||
|
||||
(note the use of `>>` to append rather than `>` to overwrite)
|
||||
|
||||
**Note:**
|
||||
For co-processor chips with multiple firmware files,
|
||||
you must put the "program" file before the "data" file.
|
||||
|
||||
Wikipedia [lists which Super Famicom games use which co-processors][wpec],
|
||||
although not all co-processors require separate firmware.
|
||||
Once you've figured out which co-processor
|
||||
(if any)
|
||||
is used by the game you want to import,
|
||||
here's the firmware files you'll need:
|
||||
|
||||
[wpec]: https://en.wikipedia.org/wiki/List_of_Super_NES_enhancement_chips#List_of_Super_NES_games_that_use_enhancement_chips
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Co-processor</th>
|
||||
<th>Filename</th>
|
||||
<th>Size (bytes)</th>
|
||||
<th>SHA256</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">CX4</th>
|
||||
<td><code>cx4.data.rom</code></td>
|
||||
<td>3072</td>
|
||||
<td><code>ae8d4d1961b93421ff00b3caa1d0f0ce7783e749772a3369c36b3dbf0d37ef18</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan=2>DSP1/1A<br><sup>See Note 1</sup></th>
|
||||
<td><code>dsp1.data.rom</code></td>
|
||||
<td>2048</td>
|
||||
<td><code>0b5da6533e55852ee8fc397977ec5576c5b9f1fb2e05656d8f87123a121b076e</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dsp1.program.rom</code></td>
|
||||
<td>6144</td>
|
||||
<td><code>269584b347a22953a2989494c850a7c1c027f4ca5add517a60e0c7d8833d0fac</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan=2>DSP1B<br><sup>See Note 2</sup></th>
|
||||
<td><code>dsp1b.data.rom</code></td>
|
||||
<td>2048</td>
|
||||
<td><code>8546cbac530830446bb8a277f6b139d4ad64d650bdbac7e4e150e2f095665049</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dsp1b.program.rom</code></td>
|
||||
<td>6144</td>
|
||||
<td><code>2eccb54a8f89374911f7e2db48f1b4cde855655e28103f7bda2982a5b418a187</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan=2>DSP2</th>
|
||||
<td><code>dsp2.data.rom</code></td>
|
||||
<td>2048</td>
|
||||
<td><code>3beef9bffdc1e84c9f99f3301d8bd3e520d2e62909a995320f9faeae8f46ec11</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dsp2.program.rom</code></td>
|
||||
<td>6144</td>
|
||||
<td><code>62a2ef8d2d7db638f4ec0fbcebf0e5bf18a75ee95be06e885d9519a10487f0da</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan="2">DSP3</th>
|
||||
<td><code>dsp3.data.rom</code></td>
|
||||
<td>2048</td>
|
||||
<td><code>7fe51796e9c97fee1fa2aab40592b7c78997f67dd00333e69d0f79a12f3cb69f</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dsp3.program.rom</code></td>
|
||||
<td>6144</td>
|
||||
<td><code>aea7b622e7c1de346cb15d16afcbedf92b6798507e179f83ed2a4cff40d0e663</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan="2">DSP4</th>
|
||||
<td><code>dsp4.data.rom</code></td>
|
||||
<td>2048</td>
|
||||
<td><code>ef3ffb4256dd896a60213269b4e2d3bdd120c97e2fd623bddabbf43c2be422af</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dsp4.program.rom</code></td>
|
||||
<td>6144</td>
|
||||
<td><code>89b1826e6038be3a0ea0f923e85d589ff6f02dc1a1819fb2ec8c0cea5b333dcd</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan="2">ST010</th>
|
||||
<td><code>st010.data.rom</code></td>
|
||||
<td>4096</td>
|
||||
<td><code>dc7056a51b53993d7a8ba5bacf9501f785d2fce5e5be748e9ff2737c5938d4a5</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>st010.program.rom</code></td>
|
||||
<td>49152</td>
|
||||
<td><code>2c1f74bb5f466d81c64c326e71ac054489efe1abc9a5d6f91aac7747f2ddab67</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan="2">ST011</th>
|
||||
<td><code>st011.data.rom</code></td>
|
||||
<td>4096</td>
|
||||
<td><code>b5377d1bebe8adc507a024b6e2b9b8fdf4877e451da84fbad05dff4e4a70311e</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>st011.program.rom</code></td>
|
||||
<td>49152</td>
|
||||
<td><code>d90a5cda380e81cb9ba11a9da7539b173c49b31bedc7a3ac9c3c8b3f97e89e14</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan="2">ST018</th>
|
||||
<td><code>st018.data.rom</code></td>
|
||||
<td>32768</td>
|
||||
<td><code>b5377d1bebe8adc507a024b6e2b9b8fdf4877e451da84fbad05dff4e4a70311e</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>st018.program.rom</code></td>
|
||||
<td>131072</td>
|
||||
<td><code>d90a5cda380e81cb9ba11a9da7539b173c49b31bedc7a3ac9c3c8b3f97e89e14</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**Note 1:**
|
||||
The DSP1 and DSP1A are physically different,
|
||||
but the firmware inside is identical.
|
||||
|
||||
**Note 2:**
|
||||
The DSP1B is very similar to the DSP1A,
|
||||
but fixes some bugs.
|
||||
Note that icarus' heuristics cannot distinguish between
|
||||
a game that uses the DSP1
|
||||
and one that uses the DSP1B,
|
||||
so if it cannot find your game in its manifest database,
|
||||
it will assume it uses DSP1B.
|
||||
Many games work just as well with either variant,
|
||||
but *Pilotwings* requires the DSP1 firmware,
|
||||
while *Ballz 3D* requires the DSP1B.
|
||||
|
||||
If you try to import a game
|
||||
using the "Import ROM Files ..." option
|
||||
in [the Library menu](../interface/higan.md#the-library-menu)
|
||||
(or using icarus directly)
|
||||
but it does not include the correct firmware data,
|
||||
a window will appear saying
|
||||
"Import completed, but with 1 errors. View log?"
|
||||
(or however many games were lacking the correct firmware).
|
||||
If you press "Yes",
|
||||
a new window will appear listing the games that couldn't be imported,
|
||||
and what problem was detected:
|
||||
|
||||
> [sbl2.sfc] ROM image is missing DSP1 firmware data
|
||||
|
||||
If you try to import a game
|
||||
using the "Load ROM File ..." option
|
||||
in [the Library menu](../interface/higan.md#the-library-menu)
|
||||
but it does not include the correct firmware data,
|
||||
nothing will happen,
|
||||
and higan will just sit there
|
||||
with "No cartridge loaded" in
|
||||
[the status bar](../interface/higan.md#the-status-bar).
|
||||
|
||||
Once a game with co-processor firmware is imported,
|
||||
you can play it just like any [regular game](#regular-games).
|
||||
|
||||
Satellaview games
|
||||
-----------------
|
||||
|
||||
The [Satellaview][wpbsx]
|
||||
was a satellite modem peripheral
|
||||
released for the Super Famicom in Japan.
|
||||
As well as the actual modem
|
||||
(designed to sit underneath the Super Famicom),
|
||||
it also included a cartridge
|
||||
with software to control the modem,
|
||||
browse online services,
|
||||
and download games and data.
|
||||
This control cartridge was called
|
||||
*BS-X Sore wa Namae o Nusumareta Machi no Monogatari*,
|
||||
which in English is
|
||||
*BS-X The Story of The Town Whose Name Was Stolen*.
|
||||
|
||||
[wpbsx]: https://en.wikipedia.org/wiki/Satellaview
|
||||
|
||||
The control cartridge had a slot that accepted
|
||||
re-writable "memory paks",
|
||||
so that people could store the games and data they downloaded.
|
||||
A small number of games that did not use the Satellaview modem
|
||||
also had a memory pak slot,
|
||||
so the game's publishers could
|
||||
publish extra content for the game
|
||||
via the Satellaview service
|
||||
after the game's release.
|
||||
For the benefit of people who didn't own a Satellaview
|
||||
some read-only memory paks
|
||||
were sold in retail stores
|
||||
containing extra content for specific games.
|
||||
|
||||
Importing a game that has a slot for a memory pak
|
||||
is just like [importing a regular game](#regular-games).
|
||||
|
||||
Importing a memory pak is like importing a regular game,
|
||||
but the name of the memory pak file *must* end in `.bs`
|
||||
(if it's in a `.zip` file,
|
||||
that's OK,
|
||||
but the name *inside* the `.zip` file
|
||||
must end in `.bs`)
|
||||
in order for it to be successfully imported.
|
||||
Sometimes memory pak filenames end in `.sfc`,
|
||||
which will make higan try to import them as
|
||||
regular Super Famicom games
|
||||
and fail.
|
||||
Rename the file and it should work beautifully.
|
||||
|
||||
Playing a game that has a slot for a memory pak
|
||||
is just like playing a regular game,
|
||||
but after you have selected which game you want to play
|
||||
higan will open another
|
||||
[filesystem browser](../interface/common.md#the-filesystem-browser)
|
||||
to let you pick which previously-imported memory pak
|
||||
you want to insert into the game.
|
||||
If you press "Cancel" at this point,
|
||||
the game will load without any cartridge in its memory pak slot.
|
||||
|
||||
If you load the control cartridge into higan,
|
||||
make sure the emulated Satellaview
|
||||
is connected to the emulated Super Famicom's expansion port
|
||||
by opening the "Super Famicom" menu,
|
||||
selecting the "Expansion Port" sub-menu,
|
||||
and choosing "Satellaview".
|
||||
If the expansion port was previously
|
||||
configured with a different option,
|
||||
power-cycle the Super Famicom
|
||||
(also in the "Super Famicom" menu)
|
||||
to make sure the control cartridge will find the Satellaview
|
||||
when it starts up.
|
||||
Note that higan's Satellaview emulation is not very accurate,
|
||||
so the control cartridge may not work as it should.
|
||||
|
||||
Playing a memory pak on its own doesn't make much sense,
|
||||
it's not a standalone cartridge.
|
||||
Play a game with a memory pak slot,
|
||||
and choose which memory pak you want when higan asks for it.
|
||||
|
||||
For more information about the Satellaview service,
|
||||
a translation patch for the control cartridge
|
||||
and emulators that do a better job of Satellaview emulation,
|
||||
see [the BS-X Project](https://bsxproj.superfamicom.org/).
|
||||
|
||||
Sufami Turbo games
|
||||
------------------
|
||||
|
||||
The [Sufami Turbo][wpst]
|
||||
was a special cartridge released
|
||||
for the Super Famicom in Japan.
|
||||
The Sufami Turbo on its own does nothing,
|
||||
but it has two slots in the top
|
||||
that accept Sufami Turbo mini-cartridges.
|
||||
The game in slot A is the one that actually plays,
|
||||
but some games can make use of additional data
|
||||
from a game in slot B.
|
||||
|
||||
Importing the Sufami Turbo cartridge
|
||||
is just like [importing a regular game](#regular-games).
|
||||
|
||||
Importing a mini-cartridge is like importing a regular game,
|
||||
but the name of the mini-cartridge file *must* end in `.st`
|
||||
(if it's in a `.zip` file,
|
||||
that's OK,
|
||||
but the name *inside* the `.zip` file
|
||||
must end in `.st`)
|
||||
in order for it to be successfully imported.
|
||||
Sometimes mini-cartridge filenames end in `.sfc`,
|
||||
which will make higan try to import them as
|
||||
regular Super Famicom games,
|
||||
and fail miserably.
|
||||
Rename the file and it should work beautifully.
|
||||
|
||||
To play a Sufami Turbo game,
|
||||
load the Sufami Turbo cartridge like any other game.
|
||||
higan will open another
|
||||
[filesystem browser](../interface/common.md#the-filesystem-browser)
|
||||
to let you pick which previously-imported mini-cartridge
|
||||
you want to insert into slot A.
|
||||
If you press "Cancel" at this point,
|
||||
the Sufami Turbo cartridge will boot without anything in slot A,
|
||||
which just displays an image telling you
|
||||
to turn off your Super Famicom,
|
||||
insert a game into slot A,
|
||||
and try again.
|
||||
If you chose a cartridge for slot A,
|
||||
higan will open yet another
|
||||
filesystem browser
|
||||
to let you choose a mini-cartridge for slot B.
|
||||
If you press "Cancel" at this point,
|
||||
the Sufami Turbo cartridge will boot without anything in slot B.
|
||||
|
||||
[wpst]: https://en.wikipedia.org/wiki/Sufami_Turbo
|
||||
|
||||
Super Game Boy games
|
||||
--------------------
|
||||
|
||||
The Super Game Boy was a special cartridge
|
||||
released for the Super Famicom
|
||||
(and all its regional variants around the world)
|
||||
that allowed Game Boy games to be played
|
||||
via the Super Famicom's controllers and video output.
|
||||
The Super Game Boy does not emulate the Game Boy hardware,
|
||||
it physically includes all the Game Boy components
|
||||
so compatibility with Game Boy games is high.
|
||||
However, the Super Game Boy drives the Game Boy hardware
|
||||
from the Super Famicom's timing signals, which means
|
||||
games play 2.4% faster than on a real Game Boy.
|
||||
|
||||
The Super Game Boy 2 was a Japan-only release
|
||||
that fixed the timing problem of the original Super Game Boy,
|
||||
and included a different set of default borders.
|
||||
higan supports the Super Game Boy 2 base cartridge,
|
||||
so you can use the extra borders,
|
||||
but does not yet emulate the timing change
|
||||
so games still play slightly too fast.
|
||||
|
||||
Because the Super Game Boy cartridge includes
|
||||
the original Game Boy hardware,
|
||||
it needs a boot ROM:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cartridge</th>
|
||||
<th>Filename</th>
|
||||
<th>Size (bytes)</th>
|
||||
<th>SHA256</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">SGB</th>
|
||||
<td><code>sgb1.boot.rom</code></td>
|
||||
<td>256</td>
|
||||
<td><code>0e4ddff32fc9d1eeaae812a157dd246459b00c9e14f2f61751f661f32361e360</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">SGB2</th>
|
||||
<td><code>sgb2.boot.rom</code></td>
|
||||
<td>256</td>
|
||||
<td><code>fd243c4fb27008986316ce3df29e9cfbcdc0cd52704970555a8bb76edbec3988</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
To import the SGB base cartridge,
|
||||
you must first combine the base cartridge data
|
||||
and the boot ROM into a single file,
|
||||
just like
|
||||
[games with co-processor firmware](#games-with-co-processor-firmware).
|
||||
Then you may import it like [a regular game](#regular-games).
|
||||
|
||||
To play a Game Boy game in Super Game Boy mode,
|
||||
load the Super Game Boy cartridge like any other game.
|
||||
higan will open another
|
||||
[filesystem browser](../interface/common.md#the-filesystem-browser)
|
||||
to let you pick which previously-imported Game Boy game
|
||||
you want to insert into the Super Game Boy.
|
||||
If you press "Cancel" at this point,
|
||||
higan will crash, so don't do that.
|
||||
|
||||
**Note:**
|
||||
Only games for the original, black-and-white Game Boy
|
||||
can be used with the Super Game Boy.
|
||||
Some games designed for the Game Boy Color
|
||||
were backward compatible with the original Game Boy
|
||||
and hence the Super Game Boy;
|
||||
see [Playing Game Boy Color games in Game Boy mode][blackcarts]
|
||||
for details.
|
||||
|
||||
[blackcarts]: ../notes.md#playing-game-boy-color-games-in-game-boy-mode
|
||||
|
||||
MSU-1 games
|
||||
-----------
|
||||
|
||||
The MSU-1 is a fictional expansion chip
|
||||
invented by higan's author byuu,
|
||||
designed to allow the Super Famicom
|
||||
to stream data and audio.
|
||||
Although the MSU-1 is not specific
|
||||
to any particular storage medium,
|
||||
it gives the Super Famicom similar capabilities
|
||||
to CD-based add-ons
|
||||
like the Mega Drive's Mega CD
|
||||
and the PC Engine's CD-ROM²,
|
||||
such as CD-quality music and full-motion video.
|
||||
|
||||
Although the MSU-1 was invented for higan,
|
||||
it is now supported by other Super Famicom emulators too.
|
||||
The [SD2SNES][sd2snes] programmable cartridge
|
||||
even allows you to play MSU-1 games on a real console.
|
||||
There are a number of homebrew games
|
||||
that make use of the MSU-1,
|
||||
and also mods for commercial Super Famicom games
|
||||
that add higher-quality music and sometimes video.
|
||||
|
||||
One thing to be aware of
|
||||
when importing an MSU-1 game
|
||||
is that early firmware versions of the SD2SNES
|
||||
had a bug that caused MSU-1 music to play too quietly.
|
||||
Skipping over [the full details][msu1vol],
|
||||
the short version is this:
|
||||
|
||||
- If offered the choice between "boosted" or non-boosted audio,
|
||||
you want the non-boosted version.
|
||||
- If an MSU-1 mod for a commercial game offers
|
||||
"emulator" and "hardware" versions of the patch file,
|
||||
it means the audio tracks are already boosted.
|
||||
- Some
|
||||
[third](https://www.zeldix.net/t1265-#18320)
|
||||
[parties](https://www.zeldix.net/t1339-#19818)
|
||||
have created replacement, non-boosted audio tracks
|
||||
for the most popular MSU-1 mods.
|
||||
If the mod you want to play has a replacement pack,
|
||||
use it with the "hardware" version of the patch.
|
||||
- Even without access to non-boosted audio tracks,
|
||||
it may be that the existing audio is only slightly boosted,
|
||||
so try the "hardware" version first, for best quality.
|
||||
- If the audio tracks are heavily boosted,
|
||||
the "hardware" patch may sound terrible,
|
||||
distorting and clipping,
|
||||
in which case try the "emulator" patch.
|
||||
|
||||
To import an MSU-1 game:
|
||||
|
||||
1. If you have a single, large file
|
||||
with the `.msu1` extension,
|
||||
that is a pack for use with [Mercurial Magic][mermag],
|
||||
which can automatically set up a game folder
|
||||
in the correct format.
|
||||
Go read Mercurial Magic's documentation
|
||||
instead of these instructions.
|
||||
2. Otherwise,
|
||||
import the Super Famicom ROM with icarus,
|
||||
[like a regular game](#regular-games).
|
||||
- If this is a homebrew game with MSU-1 support,
|
||||
there will probably be an ordinary ROM
|
||||
whose name ends in `.sfc`,
|
||||
which is the file you want to import.
|
||||
- If this is a commercial game modded for MSU-1 support,
|
||||
there will probably be a patch file
|
||||
whose name ends in `.ips` or `.bps`.
|
||||
Get a copy of the correct version of the commercial game,
|
||||
apply the patch with a tool like [Flips][flips],
|
||||
then import the patched file.
|
||||
- If there's "hardware" and "emulator" versions of the patch,
|
||||
see "One thing to be aware of..." above.
|
||||
3. Find the game folder in
|
||||
[the game library](../concepts/game-library.md)
|
||||
that icarus created when it imported the game.
|
||||
4. Copy the MSU-1 data file into the game folder.
|
||||
- This should be named `msu1.rom`
|
||||
- If there's no file by that name,
|
||||
look for a file with a `.msu` extension
|
||||
and rename it to `msu1.rom`.
|
||||
- If there's no file ending in `.msu` either,
|
||||
create an empty file named `msu1.rom`.
|
||||
5. Copy the audio tracks into the game folder.
|
||||
- If you have to choose between two sets of audio files,
|
||||
see "One thing to be aware of..." above.
|
||||
- These should be named
|
||||
`track-1.pcm`,
|
||||
`track-2.pcm`,
|
||||
... `track-9.pcm`,
|
||||
`track-10.pcm`,
|
||||
etc.
|
||||
- If there's no files with those names,
|
||||
there should be other numbered `.pcm` files
|
||||
that you can rename to match what higan expects.
|
||||
- If the `.pcm` files have no numbers in the filenames,
|
||||
there maybe a `.bml` or `.xml` file that lists
|
||||
which number goes with which file.
|
||||
- If there's no `.pcm` files at all,
|
||||
that's OK,
|
||||
this game probably just doesn't use the audio-playback feature.
|
||||
|
||||
Once the game folder is set up,
|
||||
playing an MSU-1 game is just like
|
||||
[a regular game](#regular-games).
|
||||
|
||||
[sd2snes]: https://sd2snes.de/
|
||||
[flips]: http://www.romhacking.net/utilities/1040/
|
||||
[msu1vol]: http://blog.qwertymodo.com/2017/07/the-msu-1-volume-fiasco-explained.html
|
||||
[mermag]: https://github.com/hex-usr/Mercurial-Magic/
|
||||
|
||||
Patched games
|
||||
-------------
|
||||
|
||||
The console emulation community
|
||||
has a long and vibrant history of game modding,
|
||||
or [ROM hacking][rhdn],
|
||||
including fan-translations,
|
||||
new levels for existing games,
|
||||
and more.
|
||||
Since distributing the modified versions of existing games
|
||||
would be copyright infringement,
|
||||
the changes are typically distributed as "patches",
|
||||
a file containing a list of modifications to make,
|
||||
that can be automatically applied by a "patcher" tool
|
||||
like [Flips][flips].
|
||||
|
||||
higan does not support soft-patching,
|
||||
so if you want to play a patched game in higan,
|
||||
you will need to use a patcher to apply it yourself,
|
||||
creating a new, patched copy of the game.
|
||||
|
||||
Then you can import and play the patched game just like
|
||||
[a regular game](#regular-games).
|
||||
|
||||
[rhdn]: http://www.romhacking.net/
|
||||
|
||||
Game Boy Advance games
|
||||
----------------------
|
||||
|
||||
Before you can play Game Boy Advance games,
|
||||
you must provide a copy of the Game Boy Advance BIOS.
|
||||
Unlike game-specific firmware,
|
||||
the GBA BIOS was part of the console,
|
||||
not the cartridge,
|
||||
so it must be installed
|
||||
[into higan](../install/general.md).
|
||||
|
||||
Once the GBA BIOS is installed,
|
||||
GBA games can be imported and played just like
|
||||
[any other games](#regular-games).
|
||||
|
||||
Note that some GBA games
|
||||
have trouble with
|
||||
[in-game saves](../notes#in-game-saves-and-the-game-boy-advance).
|
||||
|
||||
PowerFest '94
|
||||
-------------
|
||||
|
||||
[PowerFest '94](https://en.wikipedia.org/wiki/Nintendo_PowerFest_%2794)
|
||||
was a video game competition
|
||||
organised by Nintendo,
|
||||
in which contestants had six minutes
|
||||
to complete a challenge based on three Super Famicom games.
|
||||
The PowerFest '94 cartridge
|
||||
was custom-built for the competition,
|
||||
and included the three base games
|
||||
as well as software to run each game,
|
||||
switch between them after a specific time,
|
||||
extract a score,
|
||||
and display the combined total at the end.
|
||||
|
||||
icarus cannot automatically import
|
||||
dumps of the PowerFest '94 ROMs,
|
||||
but if you have the files,
|
||||
you can import them manually.
|
||||
|
||||
You will need the following files:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Part</th>
|
||||
<th>Filename</th>
|
||||
<th>Size (bytes)</th>
|
||||
<th>SHA256</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Scoring</th>
|
||||
<td><code>program.rom</code></td>
|
||||
<td>262144</td>
|
||||
<td><code>2fc9dca305ce3fb2f1a476567de500d50c174fbfbabd32b1b91c3ea6a731b4a1</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Super Mario Bros. - The Lost Levels</th>
|
||||
<td><code>slot-1.rom</code></td>
|
||||
<td>524288</td>
|
||||
<td><code>7fd86113c5f95f794d65807bb75ab91c93c914670c27fc813ffa2ca20a48705e</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Super Mario Kart</th>
|
||||
<td><code>slot-2.rom</code></td>
|
||||
<td>524288</td>
|
||||
<td><code>19eb77affbf8dd068f5d79a3cf80a2084fd73237cd1ae4e47192b4422449e64a</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Ken Griffey Jr. Presents Major League Baseball</th>
|
||||
<td><code>slot-3.rom</code></td>
|
||||
<td>1048576</td>
|
||||
<td><code>d47bc9f9a6289c4f2e7f6bf74095f6ed36b1043a761e3e729ac9af2fc39ae062</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
You will also need
|
||||
the usual `dsp1.program.rom` and `dsp1.data.rom`
|
||||
[co-processor firmware](#games-with-co-processor-firmware) files.
|
||||
|
||||
**Note:** the versions of
|
||||
*Super Mario Kart*
|
||||
and *Ken Griffey Jr...*
|
||||
in *PowerFest '94*
|
||||
are not the same as the stand-alone versions of those games.
|
||||
|
||||
To "import" *PowerFest '94*,
|
||||
collect all the files mentioned above, then:
|
||||
|
||||
1. Inside [the game library](../concepts/game-library.md),
|
||||
create the `Super Famicom` folder
|
||||
(if it does not already exist).
|
||||
2. Inside the `Super Famicom` folder,
|
||||
create a `PowerFest '94.sfc` folder
|
||||
(the `.sfc` extension is important,
|
||||
but you can choose a different base name if you want).
|
||||
3. Copy the various ROM files into the `PowerFest '94.sfc` folder.
|
||||
|
||||
To play *PowerFest '94*,
|
||||
open the Library menu,
|
||||
pick the Nintendo sub-menu,
|
||||
then choose the Super Famicom sub-menu item
|
||||
to open a filesystem browser listing
|
||||
all the Super Famicom games in the library.
|
||||
Select *PowerFest '94* from the list
|
||||
and click the Open button,
|
||||
or just double-click the game,
|
||||
and it will begin playing.
|
||||
@@ -1,215 +0,0 @@
|
||||
Most of the consoles higan emulates
|
||||
were designed for low resolution NTSC televisions,
|
||||
and their video output is chunky and blocky
|
||||
by today's standards.
|
||||
Video shaders customise how a console's video output
|
||||
is drawn to the computer screen,
|
||||
and can clean up and smooth out the original video,
|
||||
reproduce the scanlines and blurring of the original display,
|
||||
or any other visual effect.
|
||||
|
||||
The available video shaders are listed in
|
||||
the "Video Shaders" sub-menu of
|
||||
[the Settings menu](../interface/higan.md#the-settings-menu).
|
||||
Which shaders are available depends on
|
||||
the [video driver](drivers.md#video) higan is configured to use.
|
||||
Most drivers only support these shaders:
|
||||
|
||||
- **None**
|
||||
draws each computer pixel according to
|
||||
the colour of the single nearest console pixel,
|
||||
sometimes called "nearest neighbour" scaling.
|
||||
This produces unnaturally crisp and blocky images.
|
||||
- If you use [aspect correction or non-integral scaling][ac],
|
||||
neighbouring console pixels may be drawn
|
||||
with a different number of computer pixels due to rounding errors,
|
||||
causing a distracting rippling effect.
|
||||
- **Blur**
|
||||
draws each computer pixel according to
|
||||
the weighted average colour
|
||||
of the four nearest console pixels,
|
||||
sometimes called "bilinear" scaling.
|
||||
This produces unnaturally blurry images.
|
||||
|
||||
[ac]: ../interface/higan-settings.md#video
|
||||
|
||||
In addition to those,
|
||||
the OpenGL driver also supports custom shaders.
|
||||
|
||||
**Note:**
|
||||
For technical reasons,
|
||||
higan's emulation of certain consoles
|
||||
can produce surprising behaviour
|
||||
in certain shaders,
|
||||
particularly shaders that compare each console pixel
|
||||
with its neighbours.
|
||||
See [Video Shaders and TV-based consoles][vstv] for details.
|
||||
|
||||
[vstv]: #video-shaders-and-tv-based-consoles
|
||||
|
||||
Where to get custom shaders
|
||||
---------------------------
|
||||
|
||||
- higan includes some simple example shaders.
|
||||
If your copy of higan did not come with shaders,
|
||||
you can get them from
|
||||
[the unofficial higan repository](https://gitlab.com/higan/higan/tree/master/shaders).
|
||||
- [quark-shaders](https://github.com/hizzlekizzle/quark-shaders)
|
||||
contains many high-quality shaders for use with higan.
|
||||
- You can write your own.
|
||||
|
||||
How to install custom shaders
|
||||
-----------------------------
|
||||
|
||||
Make sure the shader you want to install
|
||||
is in the correct format:
|
||||
it should be a folder whose name ends in `.shader`,
|
||||
it should contain a file named `manifest.bml`,
|
||||
and probably some `*.fs` or `*.vs` files.
|
||||
|
||||
Place the shader folder inside
|
||||
the `Video Shaders` folder
|
||||
of your higan installation.
|
||||
If you don't have a `Video Shaders` folder,
|
||||
create it beside the `*.sys` folders
|
||||
like `Game Boy Advance.sys` and `Super Famicom.sys`.
|
||||
|
||||
- On Windows,
|
||||
this is probably the folder containing `higan.exe`
|
||||
- On Linux,
|
||||
this is probably `~/.local/share/higan`
|
||||
|
||||
Launch higan,
|
||||
open the Settings menu,
|
||||
and choose "Advanced ..."
|
||||
to open [the Advanced tab](../interface/higan-settings.md#advanced)
|
||||
of the Settings window.
|
||||
Under "Driver Selection",
|
||||
make sure "Video" is set to "OpenGL".
|
||||
If it wasn't already set that way,
|
||||
you'll need to restart higan
|
||||
for the change to take effect.
|
||||
|
||||
Open the Settings menu again,
|
||||
choose the "Video Shader" sub-menu,
|
||||
and now the shaders you installed
|
||||
should be listed at the bottom of the menu.
|
||||
|
||||
Load a game
|
||||
(so you can see the results),
|
||||
switch between shaders
|
||||
to see what they do,
|
||||
and pick your favourite!
|
||||
|
||||
Notable examples
|
||||
----------------
|
||||
|
||||
The quark-shaders repository mentioned above
|
||||
contains lots of carefully-crafted shaders,
|
||||
but some are particularly noteworthy:
|
||||
|
||||
- **AANN** implements "anti-aliased nearest neighbour" scaling.
|
||||
This uses anti-aliasing to hide
|
||||
the rounding errors often introduced by
|
||||
aspect ratio correction
|
||||
and non-integral scaling,
|
||||
producing an image nearly as crisp as the "None" shader,
|
||||
but without the distracting ripple effect.
|
||||
- **Gameboy** emulates the squarish aspect-ratio,
|
||||
greenish-colours,
|
||||
and limited palette
|
||||
of the original Game Boy.
|
||||
At larger scales,
|
||||
you can even see the fine gaps between each pixel,
|
||||
and the shadow that dark colours would cast
|
||||
on the LCD background.
|
||||
- **NTSC** performs NTSC encoding,
|
||||
bandwidth limiting,
|
||||
and NTSC decoding of the video image to recreate
|
||||
the colour fringing,
|
||||
blurring
|
||||
and shimmer
|
||||
that most game players would have seen
|
||||
on real televisions.
|
||||
Some games depend on NTSC artefacts
|
||||
to display colours outside the console's official palette
|
||||
or to create effects like transparency.
|
||||
|
||||
Video Shaders and TV-based consoles
|
||||
-----------------------------------
|
||||
|
||||
Simple shaders
|
||||
(like "None"
|
||||
and the third-party "AANN" shader)
|
||||
just blindly scale up the images they're given,
|
||||
but sophisticated shaders
|
||||
(such as the third-party "xBR" shader)
|
||||
try to produce higher-quality output
|
||||
by recognising particular patterns,
|
||||
like taking three diagonal pixels
|
||||
and turning that into a smooth diagonal line.
|
||||
|
||||
These shaders assume that
|
||||
each pixel drawn by the game's artists
|
||||
becomes a single pixel in the video output they analyze.
|
||||
The hand-held consoles that higan emulates
|
||||
(and also the Famicom)
|
||||
can only output video at one specific resolution,
|
||||
so this "one pixel equals one pixel" rule holds true,
|
||||
and pattern-based shaders like "xBR" work just fine.
|
||||
Unfortunately,
|
||||
this is not true for most of the TV-based consoles
|
||||
that higan supports.
|
||||
|
||||
The Super Famicom's "normal" video mode
|
||||
draws 256 pixels across the width of the screen,
|
||||
but the "high resolution" mode draws 512.
|
||||
Since Super Famicom games can enable hi-res mode at any time
|
||||
(even halfway through a frame),
|
||||
higan always renders Super Famicom video output 512 pixels wide,
|
||||
just in case.
|
||||
This means that in "normal" mode,
|
||||
each pixel drawn by the game's artists
|
||||
becomes two pixels in the video output,
|
||||
breaking the assumption
|
||||
that pattern-based shaders are based on.
|
||||
|
||||
The Super Famicom has a similar issue in the vertical direction:
|
||||
normally,
|
||||
an NTSC-based Super Famicom draws about 240 rows of output every frame,
|
||||
sometimes referred to as "240p" video.
|
||||
When a game turns on "interlaced" mode,
|
||||
it draws the 240 odd-numbered lines of one frame,
|
||||
then the 240 even-numbered lines of the next,
|
||||
and so forth.
|
||||
This is sometimes referred to as "480i" video.
|
||||
Although interlaced mode cannot be enabled mid-frame
|
||||
like high-resolution mode,
|
||||
resolution switching is still complex,
|
||||
so higan always draws all 480 lines of video output.
|
||||
This means for a normal, non-interlaced game,
|
||||
each pixel drawn by the game's artists
|
||||
becomes four pixels in the video output
|
||||
(two horizontally and two vertically)
|
||||
making pattern-based shaders even less useful.
|
||||
It also breaks most scanline-emulation shaders,
|
||||
since they typically draw a scanline
|
||||
for each row of pixels in the video output.
|
||||
|
||||
The Mega Drive has similar problems
|
||||
to the Super Famicom.
|
||||
It has the same behaviour with interlacing,
|
||||
but its high-resolution mode switches
|
||||
from 256 pixels across to 320 pixels across.
|
||||
Therefore in normal mode,
|
||||
each pixel drawn by the game's artists
|
||||
becomes five pixels in the video output,
|
||||
while in high-resolution mode,
|
||||
each pixel drawn by the game's artists
|
||||
becomes four pixels in the video output
|
||||
(or 10 and 8 pixels in non-interlaced mode).
|
||||
|
||||
The PC Engine does not support an interlaced mode,
|
||||
but its horizontal resolution is much more flexible
|
||||
than the Super Famicom or Mega Drive,
|
||||
and so it has the same problems with shaders as those consoles.
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
higan, the multi-system emulator
|
||||
================================
|
||||
|
||||
higan emulates a number of classic video-game consoles of the 1980s and 1990s,
|
||||
allowing you to play classic games on a modern general-purpose computer.
|
||||
|
||||
To get started with higan right away,
|
||||
see the [Quick Start](qs.md) section of the documentation.
|
||||
|
||||
|
||||
About higan
|
||||
-----------
|
||||
|
||||
As of v104,
|
||||
higan has top-tier support for the following consoles:
|
||||
|
||||
- Nintendo Super Famicom/Super Nintendo Entertainment System,
|
||||
including add-on hardware:
|
||||
- Super Game Boy
|
||||
- Sufami Turbo
|
||||
- Nintendo Game Boy Advance
|
||||
|
||||
It also includes some level of support for these consoles:
|
||||
|
||||
- Satellaview add-on for the Super Famicom
|
||||
- Nintendo Famicom/Nintendo Entertainment System
|
||||
- Nintendo Game Boy
|
||||
- Nintendo Game Boy Color
|
||||
- Sega Master System
|
||||
- Sega Game Gear
|
||||
- Sega Mega Drive/Genesis
|
||||
- NEC PC Engine/TurboGrafx 16 (but not the CD-ROM² System/TurboGrafx-CD)
|
||||
- NEC SuperGrafx
|
||||
- Bandai WonderSwan
|
||||
- Bandai WonderSwan Color
|
||||
|
||||
**Note:** Some consoles were released under different names
|
||||
in different geographic regions.
|
||||
To avoid listing all possible names
|
||||
every time such a console is mentioned,
|
||||
higan uses the name from the console's region of origin.
|
||||
In practice,
|
||||
that means Japanese names:
|
||||
"Famicom" and "Super Famicom" instead of "NES" and "SNES",
|
||||
"Mega Drive" instead of "Genesis",
|
||||
"PC Engine" instead of "TurboGrafx-16".
|
||||
|
||||
higan is actively supported on
|
||||
FreeBSD 10 and above, and
|
||||
Microsoft Windows 7 and above.
|
||||
It also includes some level of support
|
||||
for GNU/Linux and macOS.
|
||||
|
||||
higan is officially spelled with a lowercase "h", not a capital.
|
||||
|
||||
About this document
|
||||
-------------------
|
||||
|
||||
This is the semi-official higan manual,
|
||||
a community-maintained introduction and reference.
|
||||
It may be out of date
|
||||
by the time you read this,
|
||||
and it may contain errors or omissions.
|
||||
If you find something that's wrong,
|
||||
or you have a suggestion,
|
||||
post a message on the official higan forum.
|
||||
|
||||
Official higan resources
|
||||
------------------------
|
||||
|
||||
- [Official homepage](https://byuu.org/emulation/higan/)
|
||||
- [Official forum](https://board.byuu.org/viewforum.php?f=4)
|
||||
|
||||
Unofficial higan resources
|
||||
--------------------------
|
||||
|
||||
- [Source code repository](https://gitlab.com/higan/higan/)
|
||||
archives official higan releases
|
||||
and WIP snapshots
|
||||
since approximately v067r21.
|
||||
- [Quark shader repository](https://github.com/hizzlekizzle/quark-shaders)
|
||||
collects shaders that higan can use
|
||||
to add special effects like TV scanlines to its video output,
|
||||
or smarter algorithms for scaling up to modern PC resolutions.
|
||||
See [Using video shaders][shaders] below for details.
|
||||
- [Mercurial Magic](https://github.com/hex-usr/Mercurial-Magic/)
|
||||
is a tool for converting MSU-1 games and mods into a format
|
||||
higan can use.
|
||||
See [Importing MSU-1 games][msu1] for details.
|
||||
|
||||
[shaders]: guides/shaders.md
|
||||
[msu1]: guides/import.md#msu-1-games
|
||||
|
||||
There are also other projects
|
||||
based on current or older versions of higan,
|
||||
in whole or in part,
|
||||
that you might want to check out.
|
||||
|
||||
- [Mednafen](https://mednafen.github.io/)
|
||||
is another multi-system emulator.
|
||||
Its Super Famicom emulation is based on bsnes v059,
|
||||
from the time before bsnes was renamed to higan.
|
||||
- [BizHawk](http://tasvideos.org/BizHawk.html)
|
||||
is another multi-system emulator,
|
||||
specialising in the creation of
|
||||
tool-assisted speed-runs.
|
||||
Its Super Famicom emulation is based on bsnes v087.
|
||||
- [bsnes-plus](https://github.com/devinacker/bsnes-plus)
|
||||
is a fork of bsnes v073
|
||||
that adds improved support for debugging Super Famicom software.
|
||||
- [bsnes-mercury](https://github.com/libretro/bsnes-mercury/)
|
||||
is a fork of bsnes v094
|
||||
adapted to work as a
|
||||
[libretro](https://www.libretro.com/) emulation core.
|
||||
- [nSide](https://github.com/hex-usr/nSide)
|
||||
is a fork of higan that greatly enhances
|
||||
its NES emulation support,
|
||||
and adds minor features to the other cores too.
|
||||
It also restores the "balanced" Super Famicom emulation core
|
||||
that was removed from higan in v099,
|
||||
which is less CPU intensive
|
||||
than the current accuracy-focused core.
|
||||
@@ -1,53 +0,0 @@
|
||||
Installing the GBA BIOS
|
||||
-----------------------
|
||||
|
||||
For most of the systems higan emulates,
|
||||
the console itself contains (almost) no actual software,
|
||||
so emulating the system does not require
|
||||
infringing the copyright of the hardware manufacturer.
|
||||
However,
|
||||
the Game Boy Advance is different:
|
||||
every device contains a standard library of software routines
|
||||
for [common functions games require][bios],
|
||||
often called a "BIOS"
|
||||
by analogy with the Basic Input/Output System
|
||||
used in IBM PC compatibles.
|
||||
Although the GBA BIOS is required
|
||||
in order to emulate GBA games,
|
||||
it cannot be distributed with higan
|
||||
for the same legal reasons that commercial games
|
||||
cannot be distributed with higan,
|
||||
so you'll need to obtain a copy of the BIOS for yourself.
|
||||
|
||||
If you have a real GBA and a flash-cart,
|
||||
the Internet contains many tools
|
||||
that will extract the BIOS image so it can be copied
|
||||
to your desktop computer.
|
||||
The correct GBA BIOS file is exactly 16384 bytes long,
|
||||
and has the SHA256 hash
|
||||
`fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570`.
|
||||
|
||||
Once you have the correct BIOS file:
|
||||
|
||||
1. rename it to `bios.rom`
|
||||
- if you're using Windows,
|
||||
turn off "hide extensions for known file types"
|
||||
so you don't wind up with a file called
|
||||
`bios.rom.dat`
|
||||
or whatever the file's original extension was.
|
||||
2. Copy the file into higan's `Game Boy Advance.sys` folder,
|
||||
alongside the `manifest.bml` file that is already there.
|
||||
- In Windows,
|
||||
find `Game Boy Advance.sys` in the same folder
|
||||
as `higan.exe`
|
||||
- In Linux,
|
||||
find `Game Boy Advance.sys` in
|
||||
`~/.local/share/higan/`
|
||||
|
||||
**Note:**
|
||||
If you upgrade this version of higan to a newer version,
|
||||
make sure the `bios.rom` file
|
||||
winds up in the `Game Boy Advance.sys` folder
|
||||
of the new version.
|
||||
|
||||
[bios]: http://problemkaputt.de/gbatek.htm#biosfunctions
|
||||
@@ -1,142 +0,0 @@
|
||||
Compiling from source on Linux
|
||||
------------------------------
|
||||
|
||||
You will need a copy of the higan source-code.
|
||||
If you download an official release from the higan homepage,
|
||||
you will need [7-zip](http://www.7-zip.org)
|
||||
or a compatible tool to extract it.
|
||||
Alternatively,
|
||||
you may obtain higan source code from
|
||||
[the unofficial git repo](https://gitlab.com/higan/higan/)
|
||||
using the Git source-code management tool,
|
||||
or by clicking the download button on the right-hand side of the web-page
|
||||
and choosing an archive format.
|
||||
|
||||
You will also need GCC 4.9 or higher,
|
||||
including the C and C++ compiler,
|
||||
GNU Make,
|
||||
and development files
|
||||
(headers, etc.)
|
||||
for the following libraries:
|
||||
|
||||
- GTK 2.x
|
||||
- PulseAudio
|
||||
- Mesa
|
||||
- gtksourceview 2.x
|
||||
- Cairo
|
||||
- SDL 1.2
|
||||
- libXv
|
||||
- libAO
|
||||
- OpenAL
|
||||
- udev
|
||||
|
||||
On a Debian-derived Linux distribution
|
||||
(including Ubuntu and Mint),
|
||||
you can install everything you need with a command like:
|
||||
|
||||
sudo apt-get install build-essential libgtk2.0-dev libpulse-dev \
|
||||
mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl1.2-dev \
|
||||
libxv-dev libao-dev libopenal-dev libudev-dev
|
||||
|
||||
Once you have all the dependencies installed,
|
||||
you may build and install higan.
|
||||
|
||||
Note: Run these commands as yourself,
|
||||
**do not run them as root**
|
||||
(no `sudo`, no `su`, etc.),
|
||||
because higan does not support
|
||||
being installed system-wide.
|
||||
|
||||
1. Put the higan source code in some convenient location,
|
||||
like `~/higan-src`
|
||||
2. Open a terminal window
|
||||
3. Type `cd ~/higan-src`
|
||||
(or wherever you put the higan source)
|
||||
and press Enter
|
||||
4. Type `make -C higan` and press Enter
|
||||
to build the main higan executable
|
||||
5. Type `make -C icarus` and press Enter
|
||||
to build the icarus import tool
|
||||
|
||||
Installing a compiled build on Linux
|
||||
------------------------------------
|
||||
|
||||
Assuming you have successfully compiled higan
|
||||
as described in the previous section:
|
||||
|
||||
1. Open a terminal window
|
||||
2. Type `cd ~/higan-src`
|
||||
(or wherever you put the higan source)
|
||||
and press Enter
|
||||
3. Type `make -C higan install` and press Enter
|
||||
to install higan and its supporting files
|
||||
4. Type `make -C icarus install` and press Enter
|
||||
to install icarus and its game database
|
||||
5. If the higan source includes a `shaders` subdirectory,
|
||||
type `make -C shaders install`
|
||||
to install the example
|
||||
[video shaders](../guides/shaders.md).
|
||||
|
||||
This installs higan and its associated data files
|
||||
into the `~/.local` directory hierarchy.
|
||||
|
||||
To confirm higan is installed correctly,
|
||||
type `higan` in a terminal and press Enter.
|
||||
If the higan window appears,
|
||||
everything is working.
|
||||
On the other hand,
|
||||
if you get an error message like "command not found",
|
||||
you should double-check that the directory `~/.local/bin`
|
||||
is included in your `$PATH` environment variable
|
||||
by running the following command in a terminal:
|
||||
|
||||
echo "$PATH" | tr ':' '\n' | grep ~/.local/bin
|
||||
|
||||
If the above command prints the full path of `~/.local/bin`
|
||||
(for example: `/home/yourname/.local/bin`)
|
||||
then you should be good.
|
||||
If it prints nothing,
|
||||
you need to add the following line to `~/.profile`:
|
||||
|
||||
export PATH=~/.local/bin:$PATH
|
||||
|
||||
(this line must be in `~/.profile` because
|
||||
most GUIs do not read any other files at login)
|
||||
|
||||
If you also have a `~/.bash_profile`,
|
||||
make sure it reads the contents of `~/.profile`
|
||||
with a line like this:
|
||||
|
||||
source ~/.profile
|
||||
|
||||
You will need to log out and log back in
|
||||
for changes to `~/.profile` or `~/.bash_profile`
|
||||
to take effect.
|
||||
|
||||
Uninstalling a compiled build on Linux
|
||||
--------------------------------------
|
||||
|
||||
To uninstall higan,
|
||||
as installed by the above instructions:
|
||||
|
||||
1. Open a terminal window
|
||||
2. Type `cd ~/higan-src`
|
||||
(or wherever you put the higan source)
|
||||
and press Enter
|
||||
3. Type `make -C higan uninstall` and press Enter
|
||||
4. Type `make -C icarus uninstall` and press Enter
|
||||
|
||||
To remove higan's configuration,
|
||||
you should also delete the following directories
|
||||
if they exist:
|
||||
|
||||
- `~/.config/higan/`
|
||||
- `~/.config/hiro/`
|
||||
- `~/.local/share/higan/`
|
||||
- `~/.local/share/hiro/`
|
||||
|
||||
To remove the games imported into higan's library
|
||||
(including in-game saves and save-states),
|
||||
delete the directory `~/Emulation`.
|
||||
|
||||
You may also wish to delete the higan source directory.
|
||||
@@ -1,155 +0,0 @@
|
||||
Installing an official release on Windows
|
||||
-----------------------------------------
|
||||
|
||||
Official higan releases are distributed in
|
||||
[7-zip](http://www.7-zip.org/)
|
||||
archives.
|
||||
You will need to install 7-zip
|
||||
(or another compatible archiving tool)
|
||||
to install higan.
|
||||
Once you have a suitable archiving tool,
|
||||
extract the contents of the higan archive into a new folder.
|
||||
|
||||
When you're done,
|
||||
the new folder should contain `higan.exe` and `icarus.exe`
|
||||
along with other assorted files and folders
|
||||
that describe the systems higan emulates.
|
||||
You may put that new folder wherever you like.
|
||||
|
||||
To run higan, open the `higan.exe` file.
|
||||
|
||||
Uninstalling an official release on Windows
|
||||
-------------------------------------------
|
||||
|
||||
Delete the folder containing `higan.exe`
|
||||
and the other associated data from the original archive.
|
||||
|
||||
To remove higan's configuration:
|
||||
|
||||
1. Press Win+R to open the Run dialog
|
||||
2. Type `%LOCALAPPDATA%` and press Enter
|
||||
to open the folder where higan's configuration data lives
|
||||
3. Delete the sub-folders named `icarus` and `higan`
|
||||
if they exist.
|
||||
|
||||
You might also want to remove
|
||||
[higan's game library](../concepts/game-library.md#where-is-the-game-library)
|
||||
(including in-game saves and save-states).
|
||||
|
||||
Compiling from source on Windows
|
||||
--------------------------------
|
||||
|
||||
You will need a copy of the higan source-code.
|
||||
If you download an official release from the higan homepage,
|
||||
you will need [7-zip](http://www.7-zip.org/)
|
||||
or a compatible tool to extract it.
|
||||
Alternatively,
|
||||
you may obtain higan source code from
|
||||
[the unofficial git repo](https://gitlab.com/higan/higan/)
|
||||
using the Git source-code management tool,
|
||||
or by clicking the download button on the right-hand side of the web-page
|
||||
and choosing an archive format.
|
||||
|
||||
You will need a C++ compiler to compile higan.
|
||||
We recommend installing [TDM64-GCC][tdm],
|
||||
preferably the latest version
|
||||
but anything newer than 4.9 should be fine.
|
||||
higan does not support building with clang++
|
||||
(Clang is still not quite there yet for Windows)
|
||||
nor Microsoft Visual C++
|
||||
(last we checked, it didn't support all the C++ features higan uses).
|
||||
|
||||
**Note:** Make sure you get TDM64-GCC,
|
||||
not TDM-GCC.
|
||||
When compiled in x86 (32-bit) mode,
|
||||
higan may crash at startup
|
||||
because gcc targeting x86 does not support
|
||||
Windows' structured exception handling (SEH).
|
||||
Also,
|
||||
historically in x86 mode
|
||||
gcc has miscompiled a part of the NES emulation core.
|
||||
See the higan forum
|
||||
[for](https://board.byuu.org/viewtopic.php?p=41977#p41977)
|
||||
[details](https://board.byuu.org/viewtopic.php?p=42253#p42253).
|
||||
|
||||
Once you've installed the compiler,
|
||||
open a command-prompt window,
|
||||
type `g++ --version`
|
||||
then press Enter
|
||||
to check it's installed correctly.
|
||||
You should see a message like
|
||||
|
||||
```text
|
||||
g++ 1.2.3 20010101
|
||||
Copyright (C) 2001 Free Software Foundation, Inc.
|
||||
This is free software; see the source for copying conditions. There is NO
|
||||
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
```
|
||||
|
||||
...except it should mention the compiler version that you installed
|
||||
and the corresponding dates.
|
||||
If you see an error message like
|
||||
"'g++' is not recognized as an internal or external command,
|
||||
operable program or batch file",
|
||||
you may need to add the compiler's "bin" folder
|
||||
to your computer's `%PATH%`.
|
||||
See the compiler's documentation for help with that.
|
||||
|
||||
Once mingw is installed and available from the command prompt:
|
||||
|
||||
1. Put the higan source code in some convenient location,
|
||||
like `C:\higan-src`
|
||||
2. Open the command-prompt
|
||||
3. Type `cd C:\higan-src`
|
||||
(or wherever you put the higan source)
|
||||
and press Enter
|
||||
4. Type `mingw32-make -C icarus` and press Enter
|
||||
to build the icarus import tool
|
||||
5. Type `mingw32-make -C higan` and press Enter
|
||||
to build the main higan executable
|
||||
|
||||
[tdm]: http://tdm-gcc.tdragon.net/download
|
||||
|
||||
Installing a compiled build on Windows
|
||||
--------------------------------------
|
||||
|
||||
1. In Windows Explorer,
|
||||
create the folder where you want higan to live
|
||||
2. Assuming you built higan in `C:\higan-src`,
|
||||
copy `C:\higan-src\icarus\out\icarus.exe`
|
||||
into the new folder
|
||||
3. Copy `C:\higan-src\icarus\Database` and its contents
|
||||
into the new folder
|
||||
4. Copy `C:\higan-src\higan\out\higan.exe`
|
||||
into the new folder
|
||||
5. Copy all the `*.sys` folders
|
||||
in `C:\higan-src\higan\systems`
|
||||
into the new folder
|
||||
6. If the higan source includes a `shaders` folder,
|
||||
make another new folder named `Video Shaders`
|
||||
inside the new folder,
|
||||
and copy all the `*.shader` folders
|
||||
from `C:\higan-src\shaders\`
|
||||
into the `Video Shaders` folder.
|
||||
|
||||
The new folder should now contain
|
||||
`icarus.exe`,
|
||||
`higan.exe`,
|
||||
a folder named `Database`,
|
||||
and half a dozen folders named after the systems higan emulates
|
||||
with `.sys` at the end.
|
||||
This is what you would get by downloading an official build,
|
||||
as described under
|
||||
[Installing an official release on Windows][instwin]
|
||||
above.
|
||||
|
||||
[instwin]: #installing-an-official-release-on-windows
|
||||
|
||||
Uninstalling a compiled build on Windows
|
||||
----------------------------------------
|
||||
|
||||
The process is the same as
|
||||
[Uninstalling an official release on Windows][uninstwin]
|
||||
above. You may also wish to delete the higan source folder.
|
||||
|
||||
[uninstwin]: #uninstalling-an-official-release-on-windows
|
||||
@@ -1,61 +0,0 @@
|
||||
The Filesystem Browser
|
||||
----------------------
|
||||
|
||||
Sometimes higan will need you
|
||||
to choose a file or folder.
|
||||
For this, it uses a special Filesystem Browser dialog.
|
||||
Although many operating systems provide a native filesystem browser,
|
||||
they do not all allow the same customisations.
|
||||
Therefore,
|
||||
higan provides its own filesystem browser
|
||||
that works the same way on every platform.
|
||||
|
||||
The filesystem browser shows the contents of some particular folder,
|
||||
and allows you to select one of those items.
|
||||
|
||||
Across the top of the window,
|
||||
a text-box displays the path of the current folder.
|
||||
If you want to browse a specific path,
|
||||
you may edit the contents of this box
|
||||
and press Enter to switch to the new location.
|
||||
|
||||
The  button is "Refresh".
|
||||
Pressing this button will check
|
||||
if anything has been added to or removed from the current folder
|
||||
and update the list to match.
|
||||
|
||||
The  button is "Home".
|
||||
Pressing this button will switch to your home folder.
|
||||
|
||||
The  button is "Parent".
|
||||
Pressing this button will
|
||||
switch to the parent of the current folder.
|
||||
|
||||
Most of the filesystem browser lists the contents
|
||||
of the current folder.
|
||||
Double-clicking a folder,
|
||||
or selecting it and pressing Enter,
|
||||
will switch to showing the contents of that folder.
|
||||
If the list has keyboard focus,
|
||||
typing any text will jump to the first item in the list
|
||||
whose name begins with the text you typed.
|
||||
|
||||
If a drop-down list appears in the bottom-left,
|
||||
it allows you to choose which files appear in the list,
|
||||
based on file-extension.
|
||||
|
||||
If this filesystem browser is asking for a file,
|
||||
you can choose one
|
||||
by double-clicking it,
|
||||
by selecting it and pressing Enter,
|
||||
or by selecting it and clicking the "Select" button in the bottom-right.
|
||||
|
||||
If this filesystem browser is asking for a folder,
|
||||
you can choose one
|
||||
by selecting it and clicking the "Select" button in the bottom-right.
|
||||
Double-clicking
|
||||
or selecting and pressing Enter don't work,
|
||||
they just switch to viewing the contents of that folder.
|
||||
|
||||
The "Cancel" button in the bottom-right
|
||||
closes the filesystem browser without selecting anything.
|
||||
@@ -1,95 +0,0 @@
|
||||
|
||||
# Synopsis
|
||||
|
||||
> higan [*\-\-fullscreen*] [*GAME* [*SUBGAME1* [*SUBGAME2*]]]
|
||||
|
||||
# Description
|
||||
|
||||
When launched with `--fullscreen`,
|
||||
higan will automatically enter fullscreen mode
|
||||
when it starts,
|
||||
otherwise it starts in windowed mode.
|
||||
This is not much use unless you also specify `GAME`,
|
||||
because you won't be able to load a game
|
||||
until you exit fullscreen mode
|
||||
by pressing the "Toggle Fullscreen"
|
||||
[hotkey](higan-settings.md#hotkeys).
|
||||
|
||||
When `GAME` is not given,
|
||||
higan starts with no game loaded.
|
||||
|
||||
When `GAME` is the path to an existing
|
||||
[game folder](../concepts/game-folders.md)
|
||||
for any supported console,
|
||||
that game will automatically be loaded
|
||||
after higan starts.
|
||||
|
||||
When `GAME` is the path to a ROM file
|
||||
for any supported console,
|
||||
it will be imported into a game folder in
|
||||
[the Game Library](../concepts/game-library.md),
|
||||
and loaded from there after higan starts.
|
||||
|
||||
When `GAME` is of the form `REGION|PATH`
|
||||
(that is,
|
||||
a region code,
|
||||
a vertical bar (`|`),
|
||||
and a path to a game folder or ROM file)
|
||||
higan will load the game at `PATH`
|
||||
into the `REGION` variant of the appropriate console,
|
||||
rather than detecting which variant the game expects.
|
||||
This is the command-line equivalent
|
||||
of choosing a region from
|
||||
the drop-down in the bottom-right of the filesystem browser window
|
||||
when you [load a game from the game library](../guides/import.md#regular-games).
|
||||
The list of region codes differs from console to console,
|
||||
so load a game from the library
|
||||
and check the list
|
||||
before loading it from the command-line.
|
||||
|
||||
When `GAME` refers to a game with
|
||||
a [Satellaview](../guides/import.md#satellaview-games) memory pak slot,
|
||||
`SUBGAME1` should be
|
||||
the path to a game folder or ROM file
|
||||
representing the memory pak to insert into the slot.
|
||||
If `SUBGAME1` is not supplied,
|
||||
higan will prompt for one.
|
||||
`SUBGAME2` is ignored.
|
||||
|
||||
When `GAME` refers to
|
||||
the [Super Game Boy](../guides/import.md#super-game-boy-games),
|
||||
`SUBGAME1` should be
|
||||
the path to a game folder or ROM file
|
||||
representing a Game Boy game to insert into the slot.
|
||||
If `SUBGAME1` is not supplied,
|
||||
higan will prompt for one.
|
||||
`SUBGAME2` is ignored.
|
||||
|
||||
When `GAME` refers to
|
||||
the [Sufami Turbo](../guides/import.md#sufami-turbo-games),
|
||||
then `SUBGAME1` should be
|
||||
the path to a game folder or ROM file
|
||||
representing the mini-cartridge to be inserted into
|
||||
slot A of the Sufami Turbo base cartridge,
|
||||
and `SUBGAME2` is the same thing for slot B.
|
||||
higan will prompt for
|
||||
any mini-cartridges not supplied on the command-line.
|
||||
|
||||
When `GAME` refers to any other game,
|
||||
both `SUBGAME1` and `SUBGAME2` are ignored.
|
||||
|
||||
# Examples
|
||||
|
||||
Play a previously-imported copy of Super Mario World
|
||||
in fullscreen (assuming Linux defaults):
|
||||
|
||||
```sh
|
||||
higan --fullscreen "$HOME/Emulation/Super Famicom/Super Mario World.sfc"
|
||||
```
|
||||
|
||||
Play a previously-imported copy of Sonic the Hedgehog 3,
|
||||
forcing higan to emulate a Japanese Mega Drive:
|
||||
|
||||
```sh
|
||||
higan "NTSC-J|$HOME/Emulation/Mega Drive/Sonic the Hedgehog 3.md"
|
||||
```
|
||||
@@ -1,373 +0,0 @@
|
||||
The Settings window
|
||||
appears when you choose
|
||||
one of the items at the bottom of
|
||||
[the Settings menu](higan.md#the-settings-menu),
|
||||
and contains less-frequently-modified settings.
|
||||
Most of these can be safely ignored,
|
||||
or set once and never changed again.
|
||||
|
||||
This window has a tab for each main category of options:
|
||||
|
||||
Video
|
||||
=====
|
||||
|
||||
This tab contains options that affect
|
||||
how higan displays
|
||||
the emulated console's video output.
|
||||
|
||||
**Color Adjustment**
|
||||
settings adjust the colour and brightness
|
||||
of the emulated console's video output:
|
||||
|
||||
- **Saturation** adjusts the vibrancy of colours displayed,
|
||||
where 0% makes things pure grey,
|
||||
100% is normal,
|
||||
and 200% is garishly brightly coloured.
|
||||
- **Gamma** adjusts how bright mid-range colours are
|
||||
compared to the brightest colours,
|
||||
where 100% is normal,
|
||||
and 200% makes mid-range colours much darker.
|
||||
This is in addition to
|
||||
any adjustment applied by
|
||||
the "Colors" option
|
||||
in the "Video Emulation" sub-menu
|
||||
of the [Settings menu](higan.md#the-settings-menu).
|
||||
- **Luminance** adjusts the overall brightness,
|
||||
where 100% is normal,
|
||||
and 0% is totally black.
|
||||
|
||||
**Overscan Mask**
|
||||
removes parts of
|
||||
the video output that would have been hidden
|
||||
by the bezel around the edge of
|
||||
a standard-definition television screen.
|
||||
Some games (particularly on the Famicom)
|
||||
displayed random glitchy output in this area,
|
||||
which can be distracting.
|
||||
|
||||
- **Horizontal**
|
||||
removes pixels from the left and right of the video output.
|
||||
- **Vertical**
|
||||
removes pixels from the top and bottom of the video output.
|
||||
|
||||
The units are "pixels in the emulated console's standard video-mode".
|
||||
For example, setting "Horizontal" to 8
|
||||
will clip 8/256ths from the left and right sides
|
||||
of the Super Famicom's video output,
|
||||
whether the Super Famicom is in
|
||||
lo-res (256px) or hi-res (512px)
|
||||
mode.
|
||||
|
||||
**Windowed Mode**
|
||||
settings apply when higan is running
|
||||
in a normal window.
|
||||
|
||||
- **Aspect Correction**
|
||||
stretches the image to match the aspect ratio
|
||||
produced by the original console hardware,
|
||||
but can cause a "ripple" effect
|
||||
during horizontal scrolling
|
||||
due to rounding errors.
|
||||
[Video shaders](../guides/shaders.md)
|
||||
can reduce this effect.
|
||||
- **Integral Scaling**
|
||||
makes higan draw the emulated video output
|
||||
at a whole-number multiple of the original size,
|
||||
rather than completely filling the available space.
|
||||
This means that every game pixel
|
||||
uses the same number of computer pixels,
|
||||
and avoids graphics looking chunky and uneven.
|
||||
Note that Aspect Correction
|
||||
is applied after integral scaling,
|
||||
so some unevenness may be visible
|
||||
even with this option enabled.
|
||||
- **Adaptive Sizing**
|
||||
automatically resizes the higan window
|
||||
to fit snugly around the emulated video output
|
||||
whenever it changes size
|
||||
(because the user loaded a game for a different console,
|
||||
chose a different option from
|
||||
the [Video Scale sub-menu](higan.md#the-settings-menu),
|
||||
toggled Aspect Correction, etc.)
|
||||
When disabled,
|
||||
higan generally respects manual resizing.
|
||||
|
||||
**Fullscreen Mode**
|
||||
settings apply
|
||||
when higan is running fullscreen,
|
||||
because it was started with the `--fullscreen`
|
||||
[command-line option](higan-cli.md)
|
||||
or because the user pressed
|
||||
the Toggle Fullscreen [hotkey](higan-settings.md#hotkeys).
|
||||
|
||||
- **Aspect Correction**
|
||||
behaves the same way as in Windowed mode above.
|
||||
- **Integral Scaling**
|
||||
behaves the same way as in Windowed mode above.
|
||||
- **Exclusive Mode**
|
||||
requests exclusive access
|
||||
to the computer's video output
|
||||
when higan enters fullscreen mode.
|
||||
This prevents other applications
|
||||
or the operating system itself
|
||||
from drawing anything,
|
||||
and may also temporarily disable any kind of compositing,
|
||||
reducing video latency.
|
||||
As of v104,
|
||||
only the Direct3D video driver is capable of exclusive mode;
|
||||
with other drivers this option does nothing.
|
||||
|
||||
Audio
|
||||
=====
|
||||
|
||||
This tab contains options that affect
|
||||
how higan reproduces
|
||||
the emulated console's audio output.
|
||||
|
||||
**Driver**
|
||||
settings affect
|
||||
the current [Audio driver](../guides/drivers.md).
|
||||
|
||||
- **Device** allows you to choose
|
||||
which audio device higan sends
|
||||
the emulated game's audio to,
|
||||
if you have more than one.
|
||||
- **Frequency** controls the sample-rate that higan will use
|
||||
when generating audio.
|
||||
If your PC's audio hardware has a "native" sample-rate
|
||||
and you know what it is,
|
||||
pick that.
|
||||
Otherwise,
|
||||
44.1kHz or 48kHz should be fine.
|
||||
- **Latency** controls how much audio output higan calculates in advance.
|
||||
Higher values reduce the chance of
|
||||
"popping" or "glitching" noises,
|
||||
but increase the delay between an action occurring on-screen
|
||||
and the corresponding sound-effect being played.
|
||||
- **Exclusive Mode** appears
|
||||
if the current audio driver
|
||||
allows higan to take exclusive control of your PC's audio output,
|
||||
so no other applications can play sounds.
|
||||
This can improve audio quality,
|
||||
and lower the effective audio latency.
|
||||
|
||||
**Effects**
|
||||
are applied to the emulated audio output
|
||||
before it is sent to your computer's speakers.
|
||||
|
||||
- **Volume** controls the overall loudness of
|
||||
the emulated console's audio,
|
||||
where 100% is normal volume,
|
||||
and 0% is complete silence.
|
||||
- **Balance** controls the relative loudness of
|
||||
the left and right speakers,
|
||||
where 0% means only the left speaker produces sound,
|
||||
50% means both speakers produce sound equally,
|
||||
and 100% means only the right speaker produces sound.
|
||||
- **Reverb** adds a slight reverberation effect
|
||||
to the emulated console's audio output,
|
||||
as though you were playing the game in a tunnel or small room.
|
||||
|
||||
Input
|
||||
=====
|
||||
|
||||
This tab controls which PC inputs
|
||||
are mapped to which emulated controllers.
|
||||
The exact PC inputs that can be mapped
|
||||
depend on [the input driver](../guides/drivers.md#input).
|
||||
|
||||
General input settings:
|
||||
|
||||
- **Pause Emulation** automatically pauses emulation
|
||||
when the main higan window
|
||||
is not the current foreground window.
|
||||
- **Allow Input** can be ticked
|
||||
when "Pause Emulation" is *not* ticked,
|
||||
and allows configured inputs to keep affecting higan
|
||||
even when higan is running in the background.
|
||||
This is particularly relevant if
|
||||
you configure your PC keyboard to control higan:
|
||||
if you tick this box,
|
||||
and switch to a different application
|
||||
leaving higan running in the background,
|
||||
typing in that other application may affect
|
||||
the emulated game running in higan
|
||||
even though you can't see it!
|
||||
|
||||
Choosing which of the possible controllers to configure:
|
||||
|
||||
- The console selector chooses which console's inputs
|
||||
to display in the mapping list below.
|
||||
- The port selector chooses which port of the selected console
|
||||
to display in the mapping list below.
|
||||
- The controller selector chooses which controller
|
||||
associated with the given console and port
|
||||
to display in the mapping list below.
|
||||
Note that some consoles only allow particular controllers
|
||||
to be used in a particular port.
|
||||
For example,
|
||||
the Super Scope controller for the Super Famicom
|
||||
only works in Controller Port 2.
|
||||
|
||||
Configuring the selected controller:
|
||||
|
||||
- The mapping list includes
|
||||
every button and axis on the selected controller,
|
||||
and the PC inputs that will be mapped to it
|
||||
when it is connected to the selected port of the selected console.
|
||||
- **Erase** removes the mapping
|
||||
for the selected button or axis.
|
||||
- **Reset** removes all the mappings currently in the list.
|
||||
|
||||
To map
|
||||
a keyboard or gamepad button on your PC to
|
||||
a controller button,
|
||||
double-click the controller button in the list,
|
||||
or select it and press Enter.
|
||||
The Settings window will grey out,
|
||||
and a message will appear in the bottom left:
|
||||
"Press a key or button to map [the button]".
|
||||
Press the key or button you want to map,
|
||||
and it should appear in the list
|
||||
next to the controller button it is mapped to.
|
||||
|
||||
To map
|
||||
a mouse button on your PC to
|
||||
a controller button,
|
||||
select the controller button in the list,
|
||||
then click one of the "Mouse Left",
|
||||
"Mouse Middle",
|
||||
or "Mouse Right" buttons in the bottom-left of the Settings window.
|
||||
|
||||
To map
|
||||
a joystick axis on your PC to
|
||||
a controller axis,
|
||||
double-click the axis in the list,
|
||||
or select it and press Enter.
|
||||
The Settings window will grey out,
|
||||
and a message will appear in the bottom left:
|
||||
"Press a key or button to map [the axis]".
|
||||
Press the joystick in the direction you want to map,
|
||||
and it should appear in the list
|
||||
next to the controller axis it is mapped to.
|
||||
|
||||
To map
|
||||
a mouse axis on your PC to
|
||||
a controller axis,
|
||||
select the axis in the list,
|
||||
then click one of the
|
||||
"Mouse X-axis",
|
||||
or "Mouse Y-axis"
|
||||
buttons in the bottom-left of the window.
|
||||
|
||||
If you start mapping a button or axis,
|
||||
but decide you don't want to,
|
||||
you can press Escape
|
||||
to exit the "Press a key or button to map..." mode
|
||||
without actually mapping anything.
|
||||
|
||||
**Note:**
|
||||
Consoles in the Game Boy family include
|
||||
a Rumble "input" which is really more of an output.
|
||||
See [Rumble Compatibility for Game Boy (Color)][gbcrumble]
|
||||
and [Rumble Compatibility for Game Boy Advance][gbarumble]
|
||||
for details.
|
||||
|
||||
[gbcrumble]: ../notes.md#rumble-compatibility-for-game-boy-color
|
||||
[gbarumble]: ../notes.md#rumble-compatibility-for-game-boy-advance
|
||||
|
||||
**Note:**
|
||||
Once you've configured which computer inputs
|
||||
higan should route to which controllers,
|
||||
make sure the controller in question
|
||||
is actually connected to the correct controller port
|
||||
in [the console menu](higan.md#the-console-menu).
|
||||
|
||||
Hotkeys
|
||||
=======
|
||||
|
||||
This tab is like "Inputs" above,
|
||||
except it contains controls for higan itself
|
||||
instead of the emulated console.
|
||||
|
||||
- **Toggle Fullscreen** puts higan into fullscreen mode,
|
||||
where the menu and status bar are hidden,
|
||||
and the emulated console's video output
|
||||
is enlarged to cover the entire screen.
|
||||
Toggling fullscreen also automatically captures the mouse.
|
||||
- **Toggle Mouse Capture** hides the usual mouse-cursor,
|
||||
and captures the mouse so it cannot leave the higan window.
|
||||
This is useful when the mouse is being used to emulate
|
||||
a light-gun controller like the Super Scope.
|
||||
- **Save Quick State** saves the current state of the emulated console
|
||||
to the currently-selected [Quick State][qstates] slot.
|
||||
- **Load Quick State** restores the emulated console
|
||||
to the state saved in the currently-selected [Quick State][qstates] slot.
|
||||
- **Decrement Quick State** selects the previous [Quick State][qstates] slot.
|
||||
The status bar will briefly display the new current slot number.
|
||||
- **Increment Quick State** selects the next [Quick State][qstates] slot.
|
||||
The status bar will briefly display the new current slot number.
|
||||
- **Pause Emulation** pauses the emulated console
|
||||
until the Pause Emulation hotkey is pressed a second time.
|
||||
- **Fast Forward** disables audio and video synchronisation
|
||||
for as long as it's held down,
|
||||
so emulation proceeds as quickly as possible.
|
||||
If your PC struggles to hit "real time"
|
||||
(60fps for most emulated consoles),
|
||||
this likely won't have any effect.
|
||||
- **Power Cycle** turns the emulated console off and back on
|
||||
(a "hard reset"),
|
||||
just like the "Power Cycle" menu item
|
||||
in [the console menu](higan.md#the-console-menu).
|
||||
- **Rotate Display** will rotate the display
|
||||
of the Game Boy Advance
|
||||
and WonderSwan (Color).
|
||||
See [Game Boy Advance rotation](../notes.md#game-boy-advance-rotation)
|
||||
and [WonderSwan rotation](../notes.md#wonderswan-rotation)
|
||||
for details.
|
||||
|
||||
[qstates]: ../concepts/save-states.md#quick-states
|
||||
|
||||
Advanced
|
||||
========
|
||||
|
||||
This tab contains all the settings
|
||||
that didn't fit into one of the other categories.
|
||||
|
||||
**Driver Selection**
|
||||
tells higan how to
|
||||
accept input,
|
||||
display video,
|
||||
and play sound
|
||||
on this computer.
|
||||
|
||||
- **Video** controls how higan will draw
|
||||
the emulated console's video output
|
||||
to the PC screen.
|
||||
"None" means no video will be drawn.
|
||||
- **Audio** controls how higan will present
|
||||
the emulated console's audio output.
|
||||
"None" means no audio will be played.
|
||||
- **Input** controls how higan checks for input
|
||||
from the PC's input devices.
|
||||
"None" means the emulated console cannot be controlled.
|
||||
|
||||
See [Choosing drivers](../guides/drivers.md)
|
||||
for help choosing which drivers you should use.
|
||||
|
||||
**Game Library**
|
||||
configures how higan interacts
|
||||
with the [Game Library](../concepts/game-library.md).
|
||||
|
||||
- **Location** selects where higan
|
||||
looks for games to load.
|
||||
See [Moving the Game Library](../concepts/game-library.md#moving-the-game-library)
|
||||
for more information.
|
||||
- **Ignore Manifests** makes higan ignore
|
||||
the [manifest](../concepts/manifests.md) file
|
||||
in the loaded game's
|
||||
[game folder](../concepts/game-folders.md)
|
||||
in favour of asking icarus
|
||||
to guess a manifest on the fly.
|
||||
See [Ignoring manifests](../concepts/manifests.md#ignoring-manifests)
|
||||
for details.
|
||||
@@ -1,137 +0,0 @@
|
||||
The Tools window
|
||||
appears when you choose
|
||||
one of the items at the bottom of
|
||||
[the Tools menu](higan.md#the-tools-menu).
|
||||
|
||||
The window has a tab for each tool:
|
||||
|
||||
The Cheat Editor
|
||||
----------------
|
||||
|
||||
For some consoles,
|
||||
higan supports applying temporary changes to the code of a running game.
|
||||
For example,
|
||||
you could disable the code that detects when the player takes damage,
|
||||
resulting in an "invulnerability" mode.
|
||||
Currently,
|
||||
higan supports cheats for the following consoles:
|
||||
|
||||
- Famicom
|
||||
- Super Famicom
|
||||
- Game Boy
|
||||
- Master System
|
||||
- PC Engine
|
||||
- Wonder Swan
|
||||
|
||||
To add a new cheat,
|
||||
select an unused row in the list,
|
||||
then type the relevant codes in the "Code(s)" field at the bottom,
|
||||
and a description in the "Description" field.
|
||||
See [Cheat code formats](#cheat-code-formats)
|
||||
for a description of the codes higan understands.
|
||||
|
||||
To enable or disable an existing cheat,
|
||||
tick the checkbox in the first column of the list.
|
||||
The change should take effect immediately.
|
||||
|
||||
To clear out an existing cheat,
|
||||
select it from the list
|
||||
and click the "Erase" button in the bottom right,
|
||||
or just manually delete
|
||||
the contents of the "Code(s)" and "Description" fields.
|
||||
|
||||
To clear out all existing cheats,
|
||||
click the "Reset" button in the bottom right.
|
||||
|
||||
Changes made in the Cheat Editor are saved to disk
|
||||
when the game is unloaded,
|
||||
or when higan exits.
|
||||
higan stores the known cheats for a particular game
|
||||
in `higan/cheats.bml`
|
||||
inside the game's [game folder](../concepts/game-folders.md).
|
||||
|
||||
If your copy of higan includes a cheat database
|
||||
(a file named `cheats.bml`
|
||||
in the same folder as `Super Famicom.sys`
|
||||
and the other `*.sys` folders),
|
||||
you can click the "Find Codes ..." button in the bottom left
|
||||
to load all known cheats for the currently-running game.
|
||||
|
||||
Cheat code formats
|
||||
------------------
|
||||
|
||||
A cheat code of the format `addr=data`
|
||||
will cause the emulated console to obtain `data`
|
||||
whenever it reads from memory address `addr`.
|
||||
A cheat code of the format `addr=comp?data`
|
||||
will cause reads from `addr` to obtain `data`,
|
||||
but only if the true value at `addr` is `comp`.
|
||||
In both formats,
|
||||
`data` is a single byte expressed as two hexadecimal digits,
|
||||
`comp` is also a single byte expressed as two hexadecimal digits,
|
||||
and `addr` is a memory address in the emulated console,
|
||||
expressed as however many hexadecimal digits are required
|
||||
for the console in question
|
||||
(typically 4 for 8-bit CPUs,
|
||||
6 for 16-bit CPUs,
|
||||
and 8 for 32-bit CPUs).
|
||||
|
||||
For compatibility with older versions of higan,
|
||||
the older syntaxes of `addr/data` and `addr/comp/data`
|
||||
are still supported.
|
||||
|
||||
For cheats that require more than a single-byte change,
|
||||
higan allows multiple codes to be combined with `+`
|
||||
so that all of them can have a single description
|
||||
and be toggled with a single click.
|
||||
For example,
|
||||
in Super Mario World,
|
||||
you can lock the time to 999 with these codes:
|
||||
`7e0f31=09+7e0f32=09+7e0f33=09`.
|
||||
|
||||
The State Manager
|
||||
-----------------
|
||||
|
||||
The State Manager allows you to create,
|
||||
load,
|
||||
and remove Manager states.
|
||||
For more information on Manager states,
|
||||
quick states,
|
||||
saved games
|
||||
and how they compare,
|
||||
see [Save States](../concepts/save-states.md).
|
||||
|
||||
To save the current state of the loaded game
|
||||
in a new slot,
|
||||
or to replace the contents of an existing slot,
|
||||
select the slot in the list
|
||||
then click "Save" in the bottom-left corner.
|
||||
You can then type a description in the "Description" field,
|
||||
to help you find the state again later.
|
||||
|
||||
To rename a state,
|
||||
select the slot in the list
|
||||
and edit the "Description" field.
|
||||
|
||||
To load a state,
|
||||
select the slot in the list
|
||||
and click "Load" in the bottom-left corner,
|
||||
or just double-click the slot.
|
||||
|
||||
To clear the state out of a slot,
|
||||
select the slot in the list
|
||||
and click "Erase" in the bottom-right corner.
|
||||
|
||||
To clear all the slots at once,
|
||||
click "Reset" in the bottom-right corner.
|
||||
|
||||
The Manifest Viewer
|
||||
-------------------
|
||||
|
||||
As described in
|
||||
[Game Manifests](../concepts/manifests.md),
|
||||
higan uses a "manifest" to
|
||||
describe how the various parts of a game cartridge
|
||||
are wired up together.
|
||||
The Manifest Viewer lets you examine
|
||||
the configuration higan is using for the loaded game.
|
||||
@@ -1,288 +0,0 @@
|
||||
When you launch higan
|
||||
the main window appears,
|
||||
with a menu-bar across the top,
|
||||
a status-bar across the bottom,
|
||||
and a large area in the middle that shows
|
||||
the running game's video output.
|
||||
|
||||
The Library menu
|
||||
----------------
|
||||
|
||||
Manufacturer sub-menus
|
||||
allow you to play
|
||||
games you've already imported
|
||||
into higan's
|
||||
[game library](../concepts/game-library.md).
|
||||
See [Importing and playing games](../guides/import.md).
|
||||
|
||||
**Load ROM File ...**
|
||||
opens a [filesystem browser](common.md#the-filesystem-browser)
|
||||
allowing you to choose a single ROM file.
|
||||
It will be imported and immediately start playing.
|
||||
See [Importing and playing games](../guides/import.md).
|
||||
|
||||
**Import ROM Files ...**
|
||||
launches the icarus importing tool,
|
||||
allowing you to bulk-import many ROM files at once.
|
||||
See [the icarus documentation](icarus.md).
|
||||
|
||||
|
||||
The console menu
|
||||
---------------
|
||||
|
||||
**Note:**
|
||||
The console menu does not appear
|
||||
until a game is loaded.
|
||||
Also,
|
||||
it's not named "console",
|
||||
it's named for the kind of console
|
||||
the loaded game runs on.
|
||||
For example,
|
||||
when playing a Game Boy game,
|
||||
you will have a "Game Boy" menu.
|
||||
|
||||
The console menu contains commands relevant
|
||||
to the particular console being emulated.
|
||||
All consoles will have some of the following items,
|
||||
but few consoles have all of them.
|
||||
|
||||
|
||||
**Controller Port 1**
|
||||
allows you
|
||||
to connect different emulated controllers
|
||||
to the first controller port,
|
||||
if there is one.
|
||||
See [higan's Input settings](higan-settings.md#input)
|
||||
for information about configuring
|
||||
which PC controller inputs are used
|
||||
for the emulated controllers.
|
||||
|
||||
This menu appears for the Famicom,
|
||||
even though the Famicom did not support alternate controllers,
|
||||
because the Famicom emulation core also emulates the NES,
|
||||
which did.
|
||||
|
||||
**Controller Port 2**
|
||||
allows you
|
||||
to connect different emulated controllers
|
||||
to the second controller port,
|
||||
if there is one.
|
||||
See [higan's Input settings](higan-settings.md#input)
|
||||
for information about configuring
|
||||
which PC controller inputs are used
|
||||
for the emulated controllers.
|
||||
|
||||
This menu appears for the Famicom,
|
||||
even though the Famicom did not support alternate controllers,
|
||||
because the Famicom emulation core also emulates the NES,
|
||||
which did.
|
||||
|
||||
**Expansion Port**
|
||||
allows you
|
||||
to connect different emulated devices
|
||||
to the console's expansion port,
|
||||
if there is one.
|
||||
|
||||
For the Super Famicom,
|
||||
the [21fx][21fx] is a homebrew device
|
||||
that allows a program running on a PC
|
||||
to control a physical Super Famicom (or SNES).
|
||||
This option allows the same program
|
||||
to control the emulated SNES,
|
||||
for development or testing.
|
||||
|
||||
**Power Cycle**
|
||||
restarts the loaded game
|
||||
as though the emulated console were switched off and on again.
|
||||
|
||||
**Unload**
|
||||
stops the current game,
|
||||
as though the emulated console were switched off.
|
||||
You can load a new game
|
||||
from [the Library menu](#the-library-menu).
|
||||
|
||||
[21fx]: https://github.com/defparam/21FX
|
||||
|
||||
The Settings menu
|
||||
-----------------
|
||||
|
||||
The Settings menu allows you to configure things
|
||||
that aren't specific to any particular console.
|
||||
|
||||
|
||||
**Video Scale** determines the size
|
||||
of the emulated console's video output
|
||||
when higan is running in windowed mode
|
||||
(as opposed to fullscreen).
|
||||
|
||||
**Video Emulation** applies various effects
|
||||
to the emulated console's video output
|
||||
to reproduce some behaviours
|
||||
that aren't technically part of the console itself:
|
||||
|
||||
- **Blurring**
|
||||
simulates the limited horizontal resolution
|
||||
of standard-definition TVs
|
||||
by blurring together horizontally-adjacent pixels.
|
||||
For hand-held consoles,
|
||||
this simulates the slow response time
|
||||
of the cheap LCD screens these consoles used
|
||||
by blending each output frame with the previous one.
|
||||
- Games like
|
||||
*Jurassic Park* for the Super Famicom
|
||||
or *Chikyuu Kaihou Gun ZAS* for the Game Boy
|
||||
depend on this to emulate a transparency effect.
|
||||
- **Colors**
|
||||
simulates the way a console's display device
|
||||
differs from modern computer monitor's colour reproduction.
|
||||
In particular,
|
||||
it simulates the slightly-different gamma correction
|
||||
used by the Super Famicom,
|
||||
the dim, washed out colours of the original Game Boy Advance,
|
||||
and the pea-green display of the original Game Boy.
|
||||
|
||||
**Video Shader** controls
|
||||
how the low-resolution video output of the emulated console
|
||||
is scaled up to suit modern high-resolution displays.
|
||||
[Using video shaders](../guides/shaders.md)
|
||||
describes all the options in this sub-menu.
|
||||
|
||||
**Synchronize Audio**
|
||||
causes higan to wait for audio playback to complete
|
||||
before resuming emulation.
|
||||
This should reduce popping and glitching noises,
|
||||
and slows the emulation down to approximately the correct speed.
|
||||
If your PC cannot emulate at full-speed,
|
||||
(60fps for most consoles, 75fps for WonderSwan)
|
||||
this has no noticeable effect.
|
||||
|
||||
**Mute Audio**
|
||||
causes higan to not output sound from the emulated console.
|
||||
The sound hardware is still emulated.
|
||||
|
||||
**Show Status Bar**
|
||||
causes higan to show or hide the status bar
|
||||
at the bottom of the window.
|
||||
This option has no effect in fullscreen mode.
|
||||
See [The status bar](#the-status-bar) for more information.
|
||||
|
||||
**Video ...**
|
||||
opens [higan's Video settings](higan-settings.md#video).
|
||||
|
||||
**Audio ...**
|
||||
opens [higan's Audio settings](higan-settings.md#audio).
|
||||
|
||||
**Input ...**
|
||||
opens [higan's Input settings](higan-settings.md#input).
|
||||
|
||||
**Hotkeys ...**
|
||||
opens [higan's Hotkeys settings](higan-settings.md#hotkeys).
|
||||
|
||||
**Advanced ...**
|
||||
opens [higan's Advanced settings](higan-settings.md#advanced).
|
||||
|
||||
[svsa]: #why-do-synchronize-video-and-synchronize-audio-conflict
|
||||
|
||||
The Tools menu
|
||||
--------------
|
||||
|
||||
The Tools menu
|
||||
contains features for manipulating the emulated console.
|
||||
|
||||
**Save Quick State**
|
||||
stores the current state of the emulated console
|
||||
into one of the quick state slots.
|
||||
See [Save States](../concepts/save-states.md) for more information.
|
||||
|
||||
**Load Quick State**
|
||||
restores the emulated console to
|
||||
a state previously saved to one of the quick state slots.
|
||||
See [Save States](../concepts/save-states.md) for more information.
|
||||
|
||||
**Cheat Editor**
|
||||
opens [the Cheat Editor tab](higan-tools.md#the-cheat-editor)
|
||||
of the Tools window.
|
||||
|
||||
**State Manager**
|
||||
opens [the State Manager tab](higan-tools.md#the-state-manager)
|
||||
of the Tools window.
|
||||
|
||||
**Manifest Viewer**
|
||||
opens [the Manifest Viewer tab](higan-tools.md#the-manifest-viewer)
|
||||
of the Tools window.
|
||||
|
||||
The Help menu
|
||||
-------------
|
||||
|
||||
The Help menu contains information about higan itself.
|
||||
|
||||
**Documentation**
|
||||
loads the official higan documentation
|
||||
in your web-browser.
|
||||
|
||||
**Credits**
|
||||
loads a list of people who have contributed to higan
|
||||
in your web-browser.
|
||||
|
||||
**About**
|
||||
opens the About dialog,
|
||||
which displays basic information about higan,
|
||||
including the version number.
|
||||
|
||||
The status bar
|
||||
--------------
|
||||
|
||||
The status bar appears
|
||||
at the bottom of the main higan window,
|
||||
while "Show Status Bar" is ticked in [the Settings menu](#the-settings-menu).
|
||||
|
||||
Before any game is loaded,
|
||||
the status bar displays "No cartridge loaded".
|
||||
|
||||
When a game is loaded and running,
|
||||
the status bar displays the current emulation speed
|
||||
in frames-per-second.
|
||||
For PAL-based consoles,
|
||||
this should be around 50 FPS for "full speed" emulation,
|
||||
for NTSC and most portable consoles the ideal speed is 60 FPS,
|
||||
but the WonderSwan runs at 75 FPS.
|
||||
If the number is too low,
|
||||
you may need a faster computer,
|
||||
or a faster [video driver](../guides/drivers.md#video).
|
||||
If the number is too high,
|
||||
you may need to [Synchronize Audio](#the-settings-menu),
|
||||
or you may have pressed the "turbo" [hotkey](higan-settings.md#hotkeys).
|
||||
|
||||
The status bar displays "Paused"
|
||||
if you have pressed the "pause" [hotkey](higan-settings.md#hotkeys),
|
||||
or if "When focus is lost: Pause Emulation" is ticked
|
||||
in [higan's Input settings](higan-settings.md#input)
|
||||
and the main higan window is not the foreground window.
|
||||
To resume emulation,
|
||||
make sure the main higan window is in the foreground,
|
||||
and/or press the "pause" hotkey.
|
||||
|
||||
The status bar briefly displays "Selected quick state slot X"
|
||||
(where X is one of the Quick State slot numbers)
|
||||
when you press the "Increment Quick State"
|
||||
or "Decrement Quick State"
|
||||
hotkeys,
|
||||
to show which Quick State slot will be used
|
||||
the next time you press the "Save Quick State"
|
||||
or "Load Quick State" hotkeys.
|
||||
|
||||
The status bar briefly displays "Slot X quick state does not exist"
|
||||
(where X is one of the Quick State slot numbers)
|
||||
when you choose a slot from the
|
||||
[Tools](#the-tools-menu) → "Load Quick State"
|
||||
sub-menu that has not had a save-state saved to it,
|
||||
or when you press the "Load Quick State" hotkey
|
||||
while the current Quick State slot has not had a save-state saved to it,
|
||||
|
||||
The status bar briefly displays "Power cycled"
|
||||
when you choose "Power Cycle" from [the console menu](#the-console-menu),
|
||||
or press the "Power Cycle" hotkey.
|
||||
|
||||
The status bar briefly displays "Display rotation not supported"
|
||||
when you press the "Rotate Display" hotkey
|
||||
while the emulated console does not support display rotation.
|
||||
@@ -1 +0,0 @@
|
||||
../../hiro/resource/icon/go/home.png
|
||||
@@ -1,69 +0,0 @@
|
||||
When launching icarus,
|
||||
directly or by picking "Import ROM Files ..."
|
||||
from higan's [Library menu](higan.md#the-library-menu),
|
||||
the main icarus window appears.
|
||||
This window allows you to bulk-import ROM files
|
||||
into [higan's game library][gamelib],
|
||||
and also to access icarus' settings.
|
||||
|
||||
Bulk importing ROM files
|
||||
------------------------
|
||||
|
||||
icarus' main window
|
||||
is [a filesystem browser](common.md#the-filesystem-browser),
|
||||
with customisations:
|
||||
|
||||
- The filesystem browser only lists
|
||||
files with extensions typically used for ROM dumps from
|
||||
consoles higan emulates,
|
||||
plus `.zip` files since ROM dumps are often compressed.
|
||||
- Each matching file has a check-box next to it.
|
||||
- You can tick the check-box next to every file at once
|
||||
by pressing "Select All" in the bottom-left.
|
||||
- You can un-tick all the check-boxes
|
||||
by pressing "Unselect All" in the bottom-left.
|
||||
|
||||
Pressing "Import ..." in the bottom-right
|
||||
will close the filesystem browser
|
||||
then try to import all the files
|
||||
whose check-boxes are ticked
|
||||
into [the Game Library][gamelib].
|
||||
icarus displays a progress dialog during the import process,
|
||||
and a result window if any errors occurred.
|
||||
|
||||
**Note:** Some games require extra steps to import correctly;
|
||||
see [Importing and playing games](../guides/import.md) for details.
|
||||
|
||||
The icarus Settings dialog
|
||||
--------------------------
|
||||
|
||||
Pressing "Settings ..."
|
||||
in the bottom-right corner of the main icarus window
|
||||
opens the settings dialog.
|
||||
|
||||
The icarus Settings dialog contains the following settings:
|
||||
|
||||
- **Library Location** determines
|
||||
where icarus puts the games it imports.
|
||||
See [Moving the Game Library][movgamelib]
|
||||
for details.
|
||||
- **Create Manifests** causes icarus
|
||||
to include
|
||||
[a manifest file](../concepts/manifests.md)
|
||||
inside
|
||||
[the game folder](../concepts/game-folders.md)
|
||||
for each imported game.
|
||||
See [Ignoring manifests](../concepts/manifests.md#ignoring-manifests)
|
||||
for details.
|
||||
- **Use Database** causes icarus to use manifest information
|
||||
from its database of known-good manifests,
|
||||
if it's importing a game it recognises.
|
||||
For unrecognised games,
|
||||
and for all games if this box is unticked,
|
||||
icarus guesses the manifest data.
|
||||
This option is still relevant when "Create Manifests" is unticked:
|
||||
higan uses icarus to generate a manifest when a game is loaded,
|
||||
not just at import-time.
|
||||
|
||||
[gamelib]: ../concepts/game-library.md
|
||||
[movgamelib]: ../concepts/game-library.md#moving-the-game-library
|
||||
@@ -1 +0,0 @@
|
||||
../../hiro/resource/icon/action/refresh.png
|
||||
@@ -1 +0,0 @@
|
||||
../../hiro/resource/icon/go/up.png
|
||||
-219
@@ -1,219 +0,0 @@
|
||||
The consoles that higan emulates
|
||||
are similar in many ways,
|
||||
but some of them do have particular quirks of their own.
|
||||
|
||||
Music and Sound Effect Volume on the Mega Drive
|
||||
-----------------------------------------------
|
||||
|
||||
The Mega Drive has two different audio-generating chips:
|
||||
|
||||
- the SN76489 or "PSG" chip
|
||||
inherited from the Master System,
|
||||
mostly used for sound-effects
|
||||
- the YM2612 or "FM" chip,
|
||||
mostly used for music
|
||||
|
||||
With two different sound sources,
|
||||
it's important that they have similar volumes
|
||||
or one kind of sound will drown out the other.
|
||||
Sega did *not* do this,
|
||||
and different hardware revisions
|
||||
used different relative volumes.
|
||||
|
||||
higan currently
|
||||
sets the PSG volume to [125% of the FM volume][vol],
|
||||
based on [a Sega Genesis model 1 VA6][va6] that byuu owns.
|
||||
If you feel sound-effects in higan's Mega Drive emulation
|
||||
are too loud or too quiet compared to the music,
|
||||
you may be comparing it
|
||||
to a Mega Drive calibrated to a different scale
|
||||
(or to an emulator tweaked to match such a Mega Drive).
|
||||
|
||||
[vol]: https://board.byuu.org/viewtopic.php?p=42482#p42482
|
||||
[va6]: https://board.byuu.org/viewtopic.php?p=42195#p42195
|
||||
|
||||
Playing Game Boy Color games in Game Boy mode
|
||||
---------------------------------------------
|
||||
|
||||
Games for the original Game Boy
|
||||
came in solid grey cartridges,
|
||||
and only supported four-shade grey-scale graphics.
|
||||
ROM files for these games
|
||||
typically have filenames ending in `.gb`.
|
||||
|
||||
The Game Boy Color played all the original Game Boy games,
|
||||
but extended the hardware to support colour graphics.
|
||||
Games that required
|
||||
the extra hardware in the Game Boy Color
|
||||
came in transparent cartridges,
|
||||
and had a slightly different shape
|
||||
to prevent them from being used in original Game Boys.
|
||||
ROM files for these games
|
||||
typically have filenames ending in `.gbc`.
|
||||
|
||||
However,
|
||||
there were also some games
|
||||
that could use colour if it was available,
|
||||
but would stick to grey-scale if it wasn't.
|
||||
These games came in black cartridges.
|
||||
ROM files for these games
|
||||
typically have filenames ending in `.gbc`
|
||||
(since they are genuinely designed for the Game Boy Color)
|
||||
or `.gbb`.
|
||||
|
||||
Sometimes people ask
|
||||
for higan to include these backwards-compatible Game Boy Color games
|
||||
when asking for a Game Boy game to load.
|
||||
However,
|
||||
this would make higan much more complex
|
||||
for not much benefit:
|
||||
it's just the same game as in Color mode,
|
||||
but with bits chopped off.
|
||||
You might as well play backward-compatible games
|
||||
in Game Boy Color mode
|
||||
and get the full experience the developers intended.
|
||||
|
||||
If you really, really want to see
|
||||
what a particular game's backward-compatible mode looked like,
|
||||
change the filename to end with `.gb`
|
||||
(instead of `.gbc` or `.gbb`)
|
||||
before [importing it](guides/import.md).
|
||||
If you want to experiment
|
||||
with loading in-game saves from colour-mode in monochrome mode
|
||||
or vice-versa,
|
||||
you can import the game once with `.gb`
|
||||
and once with `.gbc`,
|
||||
then manually copy files between the
|
||||
[game folders](concepts/game-folders.md)
|
||||
in the "Game Boy" and "Game Boy Color" sub-folders
|
||||
of the [Game Library](concepts/game-library.md) folder.
|
||||
|
||||
Do not expect save-states to be compatible between
|
||||
Game Boy and Game Boy Color.
|
||||
|
||||
In-Game Saves and the Game Boy Advance
|
||||
--------------------------------------
|
||||
|
||||
For most of the consoles that higan emulates,
|
||||
in-game saves are simple:
|
||||
the cartridge contains some battery-backed RAM
|
||||
that the game accesses like any other memory,
|
||||
and the game's internal header usually contains some hint
|
||||
about where in memory the save data appears
|
||||
and how large it is.
|
||||
|
||||
The Game Boy Advance is different.
|
||||
By the time of the GBA,
|
||||
many save-storage technologies were available,
|
||||
most with a more complex interface than plain memory.
|
||||
Frustratingly, the GBA's internal header
|
||||
does *not* describe which storage variant the game expects.
|
||||
Therefore,
|
||||
when importing a GBA game,
|
||||
higan must guess which storage type to use
|
||||
and sometimes it guesses incorrectly.
|
||||
|
||||
If higan guesses incorrectly for a game you want to play,
|
||||
you will need to override the automatically-generated manifest.
|
||||
See [Ignoring manifests](concepts/manifests.md#ignoring-manifests)
|
||||
for details.
|
||||
|
||||
For more discussion of the GBA save type mess,
|
||||
see [What's the deal with... GBA save files?][gbasaves]
|
||||
|
||||
[gbasaves]: http://zork.net/~st/jottings/GBA_saves.html
|
||||
|
||||
Rumble compatibility for Game Boy (Color)
|
||||
-----------------------------------------
|
||||
|
||||
The Game Boy and Game Boy Color did not natively support
|
||||
any kind of rumble or force-feedback system,
|
||||
but some game cartridges (such as Pokémon Pinball)
|
||||
included a rumble motor within the cartridge itself.
|
||||
|
||||
Because higan does not currently support
|
||||
game-specific controller features,
|
||||
to experience the rumble effect in higan
|
||||
you'll need to configure the console:
|
||||
|
||||
- Open
|
||||
[higan's Input settings](interface/higan-settings.md#input)
|
||||
- In the list of consoles,
|
||||
select Game Boy, or Game Boy Color
|
||||
depending on which console you want to use to play the game
|
||||
- In the list of inputs,
|
||||
double-click "Rumble"
|
||||
or select it and press Enter
|
||||
- Press any button on the gamepad that should shake
|
||||
when the game turns on the rumble effect.
|
||||
|
||||
Rumble compatibility for Game Boy Advance
|
||||
-----------------------------------------
|
||||
|
||||
The original Game Boy Advance
|
||||
and the Game Boy Advance SP
|
||||
did not support any kind of rumble or force-feedback system,
|
||||
but the Game Boy Player add-on for the GameCube
|
||||
allowed Game Boy Advance games
|
||||
to use the rumble feature in GameCube controllers.
|
||||
|
||||
Because rumble is a feature of the Game Boy Player,
|
||||
to experience the rumble effect in higan
|
||||
you'll need to configure the console itself:
|
||||
|
||||
- Open
|
||||
[higan's Input settings](interface/higan-settings.md#input)
|
||||
- In the list of consoles,
|
||||
select Game Boy Advance
|
||||
- In the list of inputs,
|
||||
double-click "Rumble"
|
||||
or select it and press Enter
|
||||
- Press any button on the gamepad that should shake
|
||||
when the game turns on the rumble effect.
|
||||
|
||||
As well as the Game Boy Player rumble feature,
|
||||
some Game Boy Advance cartridges
|
||||
included a rumble motor within the cartridge itself.
|
||||
higan does not support this rumble technology,
|
||||
but that's not a big deal:
|
||||
the only two such games are *Drill Dozer*,
|
||||
which can use Game Boy Player rumble,
|
||||
and *WarioWare: Twisted*,
|
||||
which doesn't work anyway
|
||||
because it requires gyroscope hardware
|
||||
that higan does not yet emulate.
|
||||
|
||||
Game Boy Advance rotation
|
||||
-------------------------
|
||||
|
||||
Some Game Boy Advance homebrew games,
|
||||
as well as a bonus mode in *Dr Mario + Puzzle League*,
|
||||
expect the player to physically rotate the device
|
||||
so the screen is tall rather than wide.
|
||||
higan supports this feature with
|
||||
a Rotate [hotkey](interface/higan-settings.md#hotkeys).
|
||||
|
||||
When the user presses the Rotate hotkey,
|
||||
the console's video output is rotated 90° anti-clockwise,
|
||||
and the directional-pad controls are also rotated
|
||||
so that (for example) pushing the button for "up"
|
||||
sends the signal "right" to the emulated console,
|
||||
so that the player character moves "up" on the rotated screen.
|
||||
|
||||
WonderSwan rotation
|
||||
-------------------
|
||||
|
||||
The WonderSwan hardware
|
||||
included multiple sets of buttons
|
||||
so the player could hold the device
|
||||
vertically or horizontally.
|
||||
*Makaimura for WonderSwan* includes a level
|
||||
that requires the player to repeatedly rotate
|
||||
the device as they play.
|
||||
higan supports this feature with
|
||||
a Rotate [hotkey](interface/higan-settings.md#hotkeys).
|
||||
|
||||
When the user presses the Rotate hotkey,
|
||||
the console's video output is rotated 90° anti-clockwise,
|
||||
and the X and Y button cluster mappings
|
||||
are adjusted to match.
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
If this is your first time using higan,
|
||||
welcome!
|
||||
Here's a brief guide to getting started:
|
||||
|
||||
Install higan
|
||||
-------------
|
||||
|
||||
On Windows,
|
||||
this should be as easy as downloading
|
||||
the latest release archive from
|
||||
[the higan homepage](https://byuu.org/emulation/higan/)
|
||||
and extracting it.
|
||||
[More information...](install/windows.md)
|
||||
|
||||
On Linux,
|
||||
if your distribution doesn't already include the latest version,
|
||||
you'll need to compile it yourself — but don't worry,
|
||||
it's not too complex.
|
||||
[More information...](install/linux.md)
|
||||
|
||||
Configuring inputs
|
||||
------------------
|
||||
|
||||
Once higan's installed,
|
||||
start it up.
|
||||
Open the "Settings" menu,
|
||||
and choose "Input ..."
|
||||
to open [the Input settings](interface/higan-settings.md#input).
|
||||
|
||||
higan supports a *lot* of different controllers
|
||||
for a lot of different consoles,
|
||||
so you don't need to configure everything immediately.
|
||||
For most consoles,
|
||||
it's enough to just configure
|
||||
whatever shows up in the list
|
||||
when you choose that console.
|
||||
For example,
|
||||
when you pick the Super Famicom,
|
||||
you're shown the inputs
|
||||
for a Gamepad
|
||||
in Controller Port 1.
|
||||
|
||||
Once you've configured the controls
|
||||
just the way you want them,
|
||||
higan will remember them
|
||||
— you don't need to re-configure them
|
||||
every time you start higan,
|
||||
or every time you load a game.
|
||||
|
||||
Load a game
|
||||
-----------
|
||||
|
||||
From
|
||||
[the Library menu](interface/higan.md#the-library-menu),
|
||||
choose "Load ROM File ..."
|
||||
to open [a filesystem browser](interface/common.md#the-filesystem-browser),
|
||||
and choose the game you want to play.
|
||||
It will be added to
|
||||
[the Game Library](concepts/game-library.md),
|
||||
and it will start running immediately.
|
||||
|
||||
In the future,
|
||||
if you want to play this game again,
|
||||
you can choose "Load ROM File ..." as you did before,
|
||||
or you can choose the appropriate console name
|
||||
from the Library menu,
|
||||
which will list all the games for that console
|
||||
in the Game Library.
|
||||
|
||||
Connect a controller
|
||||
--------------------
|
||||
|
||||
Once the game is running,
|
||||
a new menu will appear
|
||||
named after the appropriate console.
|
||||
For example,
|
||||
if you're playing Super Mario World,
|
||||
that's a Super Famicom game
|
||||
so there will be a Super Famicom menu.
|
||||
|
||||
Open [the console menu](interface/higan.md#the-console-menu),
|
||||
and if there is a sub-menu for a controller port,
|
||||
make sure the port is using
|
||||
the controller you [set up previously](#configuring-inputs).
|
||||
If you configured inputs for a Gamepad in Controller Port 1,
|
||||
the Controller Port 1 sub-menu
|
||||
should be set to Gamepad.
|
||||
Like a real console,
|
||||
higan's controller ports
|
||||
start out with nothing plugged in,
|
||||
so if the game you're playing needs a gamepad connected,
|
||||
you'll have to connect it!
|
||||
|
||||
This doesn't apply to handheld consoles
|
||||
like the Game Boy and WonderSwan,
|
||||
since the "controller" is always connected.
|
||||
This *does* apply to the Famicom,
|
||||
even though the Famicom's controllers are hard-wired,
|
||||
because higan uses the name "Famicom"
|
||||
to refer to Nintendo's 8-bit home console
|
||||
in all territories,
|
||||
including the Nintendo Entertainment System.
|
||||
@@ -1,14 +0,0 @@
|
||||
Release checklist
|
||||
=================
|
||||
|
||||
1. Commit the new release
|
||||
2. Tag the commit
|
||||
3. `git push --tags origin master` to push the commit and tag at the
|
||||
same time.
|
||||
4. Go to [the docs admin][rtd] and verify that it's building the new
|
||||
version as 'stable' and under its tag name.
|
||||
5. Check out the `libretro` branch.
|
||||
6. Merge changes from master.
|
||||
7. `git push` to make the new changes available.
|
||||
|
||||
[rtd]: https://readthedocs.org/projects/higan/builds/
|
||||
@@ -1,65 +0,0 @@
|
||||
build := optimize
|
||||
include ../nall/GNUmakefile
|
||||
|
||||
binary := application
|
||||
target := tomoko
|
||||
objects := libco emulator audio video resource
|
||||
|
||||
flags += -I. -I..
|
||||
|
||||
ifeq ($(platform),windows)
|
||||
link += $(if $(call streq,$(console),true),-mconsole,-mwindows)
|
||||
ifeq ($(binary),application)
|
||||
link += -mthreads -lpthread -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
link += -Wl,-enable-auto-import
|
||||
link += -Wl,-enable-runtime-pseudo-reloc
|
||||
else ifeq ($(binary),library)
|
||||
link += -shared
|
||||
endif
|
||||
else ifeq ($(platform),macos)
|
||||
ifeq ($(binary),application)
|
||||
else ifeq ($(binary),library)
|
||||
flags += -fPIC
|
||||
link += -dynamiclib
|
||||
endif
|
||||
else ifneq ($(filter $(platform),linux bsd),)
|
||||
flags += -fopenmp
|
||||
link += -fopenmp
|
||||
ifeq ($(binary),application)
|
||||
flags += -march=native
|
||||
link += -Wl,-export-dynamic
|
||||
link += -lX11 -lXext
|
||||
else ifeq ($(binary),library)
|
||||
flags += -fPIC
|
||||
link += -shared
|
||||
endif
|
||||
else
|
||||
$(error "unsupported platform")
|
||||
endif
|
||||
|
||||
compile = \
|
||||
$(strip \
|
||||
$(if $(filter %.c,$<), \
|
||||
$(compiler) $(cflags) $(flags) $1 -c $< -o $@, \
|
||||
$(if $(filter %.cpp,$<), \
|
||||
$(compiler) $(cppflags) $(flags) $1 -c $< -o $@ \
|
||||
) \
|
||||
) \
|
||||
)
|
||||
|
||||
%.o: $<; $(call compile)
|
||||
|
||||
all: build;
|
||||
|
||||
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco)
|
||||
obj/emulator.o: emulator/emulator.cpp $(call rwildcard,emulator)
|
||||
obj/audio.o: audio/audio.cpp $(call rwildcard,audio)
|
||||
obj/video.o: video/video.cpp $(call rwildcard,video)
|
||||
obj/resource.o: resource/resource.cpp $(call rwildcard,resource)
|
||||
|
||||
ui := target-$(target)
|
||||
include $(ui)/GNUmakefile
|
||||
|
||||
clean:
|
||||
-@$(call delete,out/*)
|
||||
-@$(call delete,obj/*)
|
||||
@@ -1,99 +0,0 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
#include "stream.cpp"
|
||||
Audio audio;
|
||||
|
||||
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
|
||||
interface = nullptr;
|
||||
|
||||
if(channels_) channels = channels_();
|
||||
if(frequency_) frequency = frequency_();
|
||||
|
||||
streams.reset();
|
||||
reverb.reset();
|
||||
|
||||
reverb.resize(channels);
|
||||
for(auto c : range(channels)) {
|
||||
reverb[c].resize(7);
|
||||
reverb[c][0].resize(1229);
|
||||
reverb[c][1].resize(1559);
|
||||
reverb[c][2].resize(1907);
|
||||
reverb[c][3].resize(4057);
|
||||
reverb[c][4].resize(8117);
|
||||
reverb[c][5].resize(8311);
|
||||
reverb[c][6].resize(9931);
|
||||
}
|
||||
}
|
||||
|
||||
auto Audio::setInterface(Interface* interface) -> void {
|
||||
this->interface = interface;
|
||||
}
|
||||
|
||||
auto Audio::setFrequency(double frequency) -> void {
|
||||
this->frequency = frequency;
|
||||
for(auto& stream : streams) {
|
||||
stream->setFrequency(stream->inputFrequency, frequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto Audio::setVolume(double volume) -> void {
|
||||
this->volume = volume;
|
||||
}
|
||||
|
||||
auto Audio::setBalance(double balance) -> void {
|
||||
this->balance = balance;
|
||||
}
|
||||
|
||||
auto Audio::setReverb(bool enabled) -> void {
|
||||
this->reverbEnable = enabled;
|
||||
}
|
||||
|
||||
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
|
||||
shared_pointer<Stream> stream = new Stream;
|
||||
stream->reset(channels, frequency, this->frequency);
|
||||
streams.append(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
auto Audio::process() -> void {
|
||||
while(true) {
|
||||
for(auto& stream : streams) {
|
||||
if(!stream->pending()) return;
|
||||
}
|
||||
|
||||
double samples[channels];
|
||||
for(auto& sample : samples) sample = 0.0;
|
||||
|
||||
for(auto& stream : streams) {
|
||||
double buffer[16];
|
||||
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(reverbEnable) {
|
||||
samples[c] *= 0.125;
|
||||
for(auto n : range(7)) samples[c] += 0.125 * reverb[c][n].last();
|
||||
for(auto n : range(7)) reverb[c][n].write(samples[c]);
|
||||
samples[c] *= 8.000;
|
||||
}
|
||||
}
|
||||
|
||||
if(channels == 2) {
|
||||
if(balance < 0.0) samples[1] *= 1.0 + balance;
|
||||
if(balance > 0.0) samples[0] *= 1.0 - balance;
|
||||
}
|
||||
|
||||
platform->audioSample(samples, channels);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#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 {
|
||||
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
|
||||
auto setInterface(Interface* interface) -> void;
|
||||
|
||||
auto setFrequency(double frequency) -> void;
|
||||
auto setVolume(double volume) -> void;
|
||||
auto setBalance(double balance) -> void;
|
||||
auto setReverb(bool enabled) -> 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 = 0.0;
|
||||
|
||||
double volume = 1.0;
|
||||
double balance = 0.0;
|
||||
|
||||
bool reverbEnable = false;
|
||||
vector<vector<queue<double>>> reverb;
|
||||
|
||||
friend class Stream;
|
||||
};
|
||||
|
||||
struct Filter {
|
||||
enum class Order : uint { First, Second };
|
||||
enum class Type : uint { LowPass, HighPass };
|
||||
|
||||
Order order;
|
||||
DSP::IIR::OnePole onePole; //first-order
|
||||
DSP::IIR::Biquad biquad; //second-order
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
|
||||
|
||||
auto setFrequency(double inputFrequency, maybe<double> outputFrequency = nothing) -> void;
|
||||
|
||||
auto addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes = 1) -> void;
|
||||
|
||||
auto pending() const -> bool;
|
||||
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);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Channel {
|
||||
vector<Filter> filters;
|
||||
DSP::Resampler::Cubic resampler;
|
||||
};
|
||||
vector<Channel> channels;
|
||||
double inputFrequency;
|
||||
double outputFrequency;
|
||||
|
||||
friend class Audio;
|
||||
};
|
||||
|
||||
extern Audio audio;
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void {
|
||||
this->inputFrequency = inputFrequency;
|
||||
this->outputFrequency = outputFrequency;
|
||||
|
||||
channels.reset();
|
||||
channels.resize(channels_);
|
||||
|
||||
for(auto& channel : channels) {
|
||||
channel.filters.reset();
|
||||
channel.resampler.reset(inputFrequency, outputFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void {
|
||||
this->inputFrequency = inputFrequency;
|
||||
if(outputFrequency) this->outputFrequency = outputFrequency();
|
||||
|
||||
for(auto& channel : channels) {
|
||||
channel.resampler.reset(this->inputFrequency, this->outputFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes) -> void {
|
||||
for(auto& channel : channels) {
|
||||
for(auto pass : range(passes)) {
|
||||
Filter filter{order};
|
||||
|
||||
if(order == Filter::Order::First) {
|
||||
DSP::IIR::OnePole::Type _type;
|
||||
if(type == Filter::Type::LowPass) _type = DSP::IIR::OnePole::Type::LowPass;
|
||||
if(type == Filter::Type::HighPass) _type = DSP::IIR::OnePole::Type::HighPass;
|
||||
filter.onePole.reset(_type, cutoffFrequency, inputFrequency);
|
||||
}
|
||||
|
||||
if(order == Filter::Order::Second) {
|
||||
DSP::IIR::Biquad::Type _type;
|
||||
if(type == Filter::Type::LowPass) _type = DSP::IIR::Biquad::Type::LowPass;
|
||||
if(type == Filter::Type::HighPass) _type = DSP::IIR::Biquad::Type::HighPass;
|
||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.biquad.reset(_type, cutoffFrequency, inputFrequency, q);
|
||||
}
|
||||
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::pending() const -> bool {
|
||||
return channels && channels[0].resampler.pending();
|
||||
}
|
||||
|
||||
auto Stream::read(double samples[]) -> uint {
|
||||
for(auto c : range(channels)) samples[c] = channels[c].resampler.read();
|
||||
return channels.size();
|
||||
}
|
||||
|
||||
auto Stream::write(const double samples[]) -> void {
|
||||
for(auto c : range(channels)) {
|
||||
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
|
||||
for(auto& filter : channels[c].filters) {
|
||||
switch(filter.order) {
|
||||
case Filter::Order::First: sample = filter.onePole.process(sample); break;
|
||||
case Filter::Order::Second: sample = filter.biquad.process(sample); break;
|
||||
}
|
||||
}
|
||||
channels[c].resampler.write(sample);
|
||||
}
|
||||
|
||||
audio.process();
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="higan" version="1.0.0.0" processorArchitecture="*"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||
<dpiAware>false</dpiAware>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
||||
@@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=higan
|
||||
Comment=Emulator
|
||||
Exec=higan
|
||||
Icon=higan
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Game;Emulator;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB |
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.byuu.higan</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>higan</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>higan</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>higan.icns</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,2 +0,0 @@
|
||||
1 24 "higan.Manifest"
|
||||
2 ICON DISCARDABLE "higan.ico"
|
||||
@@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="256mm"
|
||||
height="256mm"
|
||||
viewBox="0 0 256 256"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||
sodipodi:docname="higan.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.5"
|
||||
inkscape:cx="-264.38189"
|
||||
inkscape:cy="632.78774"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1028"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-41)">
|
||||
<circle
|
||||
id="path10"
|
||||
cx="128.0"
|
||||
cy="169.0"
|
||||
r="120.0"
|
||||
style="stroke-width:0.25;fill:#ffc0c0;fill-opacity:1" />
|
||||
<g
|
||||
aria-label="火"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:254.9591217px;line-height:1.25;font-family:KaiTi;-inkscape-font-specification:KaiTi;letter-spacing:0px;word-spacing:0px;fill:#e01818;fill-opacity:1;stroke:#e01818;stroke-width:9;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="text818"
|
||||
transform="translate(318.55834,64.029166)">
|
||||
<path
|
||||
style="fill:#e01818;fill-opacity:1;stroke:#e01818;stroke-width:9;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -194.12628,101.7548 q 15.93495,23.90242 34.85769,43.8211 19.91869,19.91868 37.8455,30.87396 18.92275,9.95934 40.833295,16.93088 21.91055,5.9756 -2.987802,7.96747 -24.898353,1.99187 -37.845493,0 -11.95121,-0.99594 -27.88616,-21.91055 -14.93901,-20.91462 -46.8089,-66.72758 -5.9756,25.89428 -13.94307,41.82923 -7.96748,14.93901 -26.89022,30.87395 -18.92275,14.93901 -39.83737,17.92682 -19.91868,3.98373 -3.98373,-3.98374 16.93088,-6.97154 33.86176,-21.91055 17.92681,-14.93901 26.89022,-34.85769 8.9634,-20.91462 10.95527,-50.79264 2.9878,-29.878022 1.99187,-50.792637 0,-20.914616 -4.97967,-28.882088 -4.97967,-8.963407 2.9878,-5.9756048 8.96341,1.9918681 16.93088,5.9756048 8.96341,2.987802 5.9756,8.963406 -2.9878,4.979671 -5.9756,33.861759 -1.99187,27.886153 -1.99187,46.8089 z"
|
||||
id="path827"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#e01818;fill-opacity:1;stroke:#e01818;stroke-width:9;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -110.28062,50.929737 q 7.96747,6.971538 0,8.963407 -6.97154,0.995934 -22.90648,10.955274 -14.93901,8.963407 -28.88209,15.934945 -12.94714,5.975604 -1.99187,-3.983736 11.95121,-10.955275 21.91055,-21.910549 10.95528,-10.955275 10.95528,-17.926814 0,-7.967472 6.97153,-3.983736 6.97154,3.983736 13.94308,11.951209 z"
|
||||
id="path825"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#e01818;fill-opacity:1;stroke:#e01818;stroke-width:9;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -261.66505,69.757659 q -15.93494,-24.898352 0,-15.934945 15.93495,7.967472 20.91462,14.939011 4.97967,5.975604 0.99593,16.930876 -3.98373,10.95528 -21.91055,-15.934942 z"
|
||||
id="path820"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Cheat {
|
||||
struct Code {
|
||||
uint addr;
|
||||
uint data;
|
||||
maybe<uint> comp;
|
||||
};
|
||||
|
||||
explicit operator bool() const {
|
||||
return codes.size() > 0;
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
codes.reset();
|
||||
}
|
||||
|
||||
auto append(uint addr, uint data, maybe<uint> comp = nothing) -> void {
|
||||
codes.append({addr, data, comp});
|
||||
}
|
||||
|
||||
auto assign(const string_vector& 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 addr, uint comp) -> maybe<uint> {
|
||||
for(auto& code : codes) {
|
||||
if(code.addr == addr && (!code.comp || code.comp() == comp)) {
|
||||
return code.data;
|
||||
}
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private:
|
||||
vector<Code> codes;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
Platform* platform = nullptr;
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/vfs.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include "types.hpp"
|
||||
#include <libco/libco.h>
|
||||
#include <audio/audio.hpp>
|
||||
#include <video/video.hpp>
|
||||
#include <resource/resource.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "106";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "https://byuu.org/";
|
||||
|
||||
//incremented only when serialization format changes
|
||||
static const string SerializerVersion = "104";
|
||||
|
||||
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"
|
||||
@@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Interface {
|
||||
struct Information {
|
||||
string manufacturer;
|
||||
string name;
|
||||
bool overscan = false;
|
||||
bool resettable = false;
|
||||
} information;
|
||||
|
||||
struct Medium {
|
||||
uint id;
|
||||
string name;
|
||||
string type; //extension
|
||||
};
|
||||
vector<Medium> media;
|
||||
|
||||
struct Device {
|
||||
uint id;
|
||||
string name;
|
||||
struct Input {
|
||||
uint type; //0 = digital, 1 = analog (relative), 2 = rumble
|
||||
string name;
|
||||
};
|
||||
vector<Input> inputs;
|
||||
};
|
||||
|
||||
struct Port {
|
||||
uint id;
|
||||
string name;
|
||||
vector<Device> devices;
|
||||
};
|
||||
vector<Port> ports;
|
||||
|
||||
//information
|
||||
virtual auto manifest() -> string = 0;
|
||||
virtual auto title() -> string = 0;
|
||||
|
||||
struct VideoInformation {
|
||||
uint width = 0;
|
||||
uint height = 0;
|
||||
uint internalWidth = 0;
|
||||
uint internalHeight = 0;
|
||||
double aspectCorrection = 0;
|
||||
double refreshRate = 0;
|
||||
};
|
||||
virtual auto videoInformation() -> VideoInformation = 0;
|
||||
virtual auto videoColors() -> uint32 = 0;
|
||||
virtual auto videoColor(uint32 color) -> uint64 = 0;
|
||||
|
||||
//media interface
|
||||
virtual auto loaded() -> bool { return false; }
|
||||
virtual auto sha256() -> string { return ""; }
|
||||
virtual auto load(uint id) -> bool { return false; }
|
||||
virtual auto save() -> void {}
|
||||
virtual auto unload() -> void {}
|
||||
|
||||
//system interface
|
||||
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 rtcSynchronize() -> void {}
|
||||
|
||||
//state functions
|
||||
virtual auto serialize() -> serializer = 0;
|
||||
virtual auto unserialize(serializer&) -> bool = 0;
|
||||
|
||||
//cheat functions
|
||||
virtual auto cheatSet(const string_vector& = {}) -> void {}
|
||||
|
||||
//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; }
|
||||
|
||||
//shared functions
|
||||
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Platform {
|
||||
struct Load {
|
||||
Load() : _pathID(nothing) {}
|
||||
Load(uint pathID, string option = "") : _pathID(pathID), _option(option) {}
|
||||
|
||||
explicit operator bool() const { return (bool)_pathID; }
|
||||
auto pathID() const -> uint { return _pathID(); }
|
||||
auto option() const -> string { return _option; }
|
||||
|
||||
private:
|
||||
maybe<uint> _pathID;
|
||||
string _option;
|
||||
};
|
||||
|
||||
virtual auto path(uint id) -> string { return ""; }
|
||||
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; }
|
||||
virtual auto load(uint id, string name, string type, string_vector options = {}) -> Load { return {}; }
|
||||
virtual auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {}
|
||||
virtual auto audioSample(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 { print(text, "\n"); }
|
||||
};
|
||||
|
||||
extern Platform* platform;
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#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.bit(lobit) ? lovalue : hivalue;
|
||||
if(address.bit(hibit)) value = ~value;
|
||||
if((random() & 511) == 0) value.bit(random() & 7) ^= 1;
|
||||
if((random() & 2047) == 0) value.bit(random() & 7) ^= 1;
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Scheduler {
|
||||
enum class Mode : uint {
|
||||
Run,
|
||||
SynchronizeMaster,
|
||||
SynchronizeSlave,
|
||||
};
|
||||
|
||||
enum class Event : uint {
|
||||
Step,
|
||||
Frame,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
inline auto synchronizing() const -> bool { return _mode == Mode::SynchronizeSlave; }
|
||||
|
||||
auto reset() -> void {
|
||||
_host = co_active();
|
||||
_threads.reset();
|
||||
}
|
||||
|
||||
auto primary(Thread& thread) -> void {
|
||||
_master = _resume = thread.handle();
|
||||
}
|
||||
|
||||
auto append(Thread& thread) -> bool {
|
||||
if(_threads.find(&thread)) return false;
|
||||
thread._clock += _threads.size(); //this bias prioritizes threads appended earlier first
|
||||
return _threads.append(&thread), true;
|
||||
}
|
||||
|
||||
auto remove(Thread& thread) -> bool {
|
||||
if(auto offset = _threads.find(&thread)) return _threads.remove(*offset), true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto enter(Mode mode = Mode::Run) -> Event {
|
||||
_mode = mode;
|
||||
_host = co_active();
|
||||
co_switch(_resume);
|
||||
return _event;
|
||||
}
|
||||
|
||||
inline auto resume(Thread& thread) -> void {
|
||||
if(_mode != Mode::SynchronizeSlave) co_switch(thread.handle());
|
||||
}
|
||||
|
||||
auto exit(Event event) -> void {
|
||||
uintmax minimum = -1;
|
||||
for(auto thread : _threads) {
|
||||
if(thread->_clock < minimum) minimum = thread->_clock;
|
||||
}
|
||||
for(auto thread : _threads) {
|
||||
thread->_clock -= minimum;
|
||||
}
|
||||
|
||||
_event = event;
|
||||
_resume = co_active();
|
||||
co_switch(_host);
|
||||
}
|
||||
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(thread.handle() == _master) {
|
||||
while(enter(Mode::SynchronizeMaster) != Event::Synchronize);
|
||||
} else {
|
||||
_resume = thread.handle();
|
||||
while(enter(Mode::SynchronizeSlave) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
inline auto synchronize() -> void {
|
||||
if(co_active() == _master) {
|
||||
if(_mode == Mode::SynchronizeMaster) return exit(Event::Synchronize);
|
||||
} else {
|
||||
if(_mode == Mode::SynchronizeSlave) return exit(Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
cothread_t _host = nullptr; //program thread (used to exit scheduler)
|
||||
cothread_t _resume = nullptr; //resume thread (used to enter scheduler)
|
||||
cothread_t _master = nullptr; //primary thread (used to synchronize components)
|
||||
Mode _mode = Mode::Run;
|
||||
Event _event = Event::Step;
|
||||
vector<Thread*> _threads;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Thread {
|
||||
enum : uintmax { Second = (uintmax)-1 >> 1 };
|
||||
|
||||
virtual ~Thread() {
|
||||
if(_handle) co_delete(_handle);
|
||||
}
|
||||
|
||||
inline auto active() const { return co_active() == _handle; }
|
||||
inline auto handle() const { return _handle; }
|
||||
inline auto frequency() const { return _frequency; }
|
||||
inline auto scalar() const { return _scalar; }
|
||||
inline auto clock() const { return _clock; }
|
||||
|
||||
auto setFrequency(double frequency) -> void {
|
||||
_frequency = frequency + 0.5;
|
||||
_scalar = Second / _frequency;
|
||||
}
|
||||
|
||||
auto setScalar(uintmax scalar) -> void {
|
||||
_scalar = scalar;
|
||||
}
|
||||
|
||||
auto setClock(uintmax clock) -> void {
|
||||
_clock = clock;
|
||||
}
|
||||
|
||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||
if(_handle) co_delete(_handle);
|
||||
_handle = co_create(64 * 1024 * sizeof(void*), entrypoint);
|
||||
setFrequency(frequency);
|
||||
setClock(0);
|
||||
}
|
||||
|
||||
inline auto step(uint clocks) -> void {
|
||||
_clock += _scalar * clocks;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(_frequency);
|
||||
s.integer(_scalar);
|
||||
s.integer(_clock);
|
||||
}
|
||||
|
||||
protected:
|
||||
cothread_t _handle = nullptr;
|
||||
uintmax _frequency = 0;
|
||||
uintmax _scalar = 0;
|
||||
uintmax _clock = 0;
|
||||
|
||||
friend class Scheduler;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
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 = nall::Integer< 8>;
|
||||
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 = nall::Integer<16>;
|
||||
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 = nall::Integer<32>;
|
||||
using int33 = nall::Integer<33>;
|
||||
using int34 = nall::Integer<34>;
|
||||
using int35 = nall::Integer<35>;
|
||||
using int36 = nall::Integer<36>;
|
||||
using int37 = nall::Integer<37>;
|
||||
using int38 = nall::Integer<38>;
|
||||
using int39 = nall::Integer<39>;
|
||||
using int40 = nall::Integer<40>;
|
||||
using int41 = nall::Integer<41>;
|
||||
using int42 = nall::Integer<42>;
|
||||
using int43 = nall::Integer<43>;
|
||||
using int44 = nall::Integer<44>;
|
||||
using int45 = nall::Integer<45>;
|
||||
using int46 = nall::Integer<46>;
|
||||
using int47 = nall::Integer<47>;
|
||||
using int48 = nall::Integer<48>;
|
||||
using int49 = nall::Integer<49>;
|
||||
using int50 = nall::Integer<50>;
|
||||
using int51 = nall::Integer<51>;
|
||||
using int52 = nall::Integer<52>;
|
||||
using int53 = nall::Integer<53>;
|
||||
using int54 = nall::Integer<54>;
|
||||
using int55 = nall::Integer<55>;
|
||||
using int56 = nall::Integer<56>;
|
||||
using int57 = nall::Integer<57>;
|
||||
using int58 = nall::Integer<58>;
|
||||
using int59 = nall::Integer<59>;
|
||||
using int60 = nall::Integer<60>;
|
||||
using int61 = nall::Integer<61>;
|
||||
using int62 = nall::Integer<62>;
|
||||
using int63 = nall::Integer<63>;
|
||||
using int64 = nall::Integer<64>;
|
||||
|
||||
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 = nall::Natural< 8>;
|
||||
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 = nall::Natural<16>;
|
||||
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 = nall::Natural<32>;
|
||||
using uint33 = nall::Natural<33>;
|
||||
using uint34 = nall::Natural<34>;
|
||||
using uint35 = nall::Natural<35>;
|
||||
using uint36 = nall::Natural<36>;
|
||||
using uint37 = nall::Natural<37>;
|
||||
using uint38 = nall::Natural<38>;
|
||||
using uint39 = nall::Natural<39>;
|
||||
using uint40 = nall::Natural<40>;
|
||||
using uint41 = nall::Natural<41>;
|
||||
using uint42 = nall::Natural<42>;
|
||||
using uint43 = nall::Natural<43>;
|
||||
using uint44 = nall::Natural<44>;
|
||||
using uint45 = nall::Natural<45>;
|
||||
using uint46 = nall::Natural<46>;
|
||||
using uint47 = nall::Natural<47>;
|
||||
using uint48 = nall::Natural<48>;
|
||||
using uint49 = nall::Natural<49>;
|
||||
using uint50 = nall::Natural<50>;
|
||||
using uint51 = nall::Natural<51>;
|
||||
using uint52 = nall::Natural<52>;
|
||||
using uint53 = nall::Natural<53>;
|
||||
using uint54 = nall::Natural<54>;
|
||||
using uint55 = nall::Natural<55>;
|
||||
using uint56 = nall::Natural<56>;
|
||||
using uint57 = nall::Natural<57>;
|
||||
using uint58 = nall::Natural<58>;
|
||||
using uint59 = nall::Natural<59>;
|
||||
using uint60 = nall::Natural<60>;
|
||||
using uint61 = nall::Natural<61>;
|
||||
using uint62 = nall::Natural<62>;
|
||||
using uint63 = nall::Natural<63>;
|
||||
using uint64 = nall::Natural<64>;
|
||||
@@ -1,13 +0,0 @@
|
||||
processors += mos6502
|
||||
|
||||
objects += fc-interface fc-system fc-controller
|
||||
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
|
||||
|
||||
obj/fc-interface.o: fc/interface/interface.cpp $(call rwildcard,fc/interface/)
|
||||
obj/fc-system.o: fc/system/system.cpp $(call rwildcard,fc/system/)
|
||||
obj/fc-controller.o: fc/controller/controller.cpp $(call rwildcard,fc/controller/)
|
||||
obj/fc-memory.o: fc/memory/memory.cpp $(call rwildcard,fc/memory/)
|
||||
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp $(call rwildcard,fc/cartridge/)
|
||||
obj/fc-cpu.o: fc/cpu/cpu.cpp $(call rwildcard,fc/cpu/)
|
||||
obj/fc-apu.o: fc/apu/apu.cpp $(call rwildcard,fc/apu/)
|
||||
obj/fc-ppu.o: fc/ppu/ppu.cpp $(call rwildcard,fc/ppu/)
|
||||
@@ -1,319 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "envelope.cpp"
|
||||
#include "sweep.cpp"
|
||||
#include "pulse.cpp"
|
||||
#include "triangle.cpp"
|
||||
#include "noise.cpp"
|
||||
#include "dmc.cpp"
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
APU::APU() {
|
||||
for(uint amp : range(32)) {
|
||||
if(amp == 0) {
|
||||
pulseDAC[amp] = 0;
|
||||
} else {
|
||||
pulseDAC[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
for(uint dmc_amp : range(128)) {
|
||||
for(uint triangle_amp : range(16)) {
|
||||
for(uint noise_amp : range(16)) {
|
||||
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
|
||||
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp] = 0;
|
||||
} else {
|
||||
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp]
|
||||
= 16384.0 * 159.79 / (100.0 + 1.0 / (triangle_amp / 8227.0 + noise_amp / 12241.0 + dmc_amp / 22638.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
uint pulse_output, triangle_output, noise_output, dmc_output;
|
||||
|
||||
pulse_output = pulse[0].clock();
|
||||
pulse_output += pulse[1].clock();
|
||||
triangle_output = triangle.clock();
|
||||
noise_output = noise.clock();
|
||||
dmc_output = dmc.clock();
|
||||
|
||||
clockFrameCounterDivider();
|
||||
|
||||
int output = 0;
|
||||
output += pulseDAC[pulse_output];
|
||||
output += dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
|
||||
output += cartridgeSample;
|
||||
stream->sample(sclamp<16>(output) / 32768.0);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto APU::tick() -> void {
|
||||
Thread::step(rate());
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto APU::setIRQ() -> void {
|
||||
cpu.apuLine(frame.irqPending || dmc.irqPending);
|
||||
}
|
||||
|
||||
auto APU::setSample(int16 sample) -> void {
|
||||
cartridgeSample = sample;
|
||||
}
|
||||
|
||||
auto APU::power(bool reset) -> void {
|
||||
create(APU::Enter, system.frequency());
|
||||
stream = Emulator::audio.createStream(1, frequency() / rate());
|
||||
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 90.0);
|
||||
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 440.0);
|
||||
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 14000.0);
|
||||
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
|
||||
|
||||
pulse[0].power();
|
||||
pulse[1].power();
|
||||
triangle.power();
|
||||
noise.power();
|
||||
dmc.power();
|
||||
|
||||
frame.irqPending = 0;
|
||||
|
||||
frame.mode = 0;
|
||||
frame.counter = 0;
|
||||
frame.divider = 1;
|
||||
|
||||
enabledChannels = 0;
|
||||
cartridgeSample = 0;
|
||||
|
||||
setIRQ();
|
||||
}
|
||||
|
||||
auto APU::readIO(uint16 addr) -> uint8 {
|
||||
switch(addr) {
|
||||
|
||||
case 0x4015: {
|
||||
uint8 result = 0x00;
|
||||
result |= pulse[0].lengthCounter ? 0x01 : 0;
|
||||
result |= pulse[1].lengthCounter ? 0x02 : 0;
|
||||
result |= triangle.lengthCounter ? 0x04 : 0;
|
||||
result |= noise.lengthCounter ? 0x08 : 0;
|
||||
result |= dmc.lengthCounter ? 0x10 : 0;
|
||||
result |= frame.irqPending ? 0x40 : 0;
|
||||
result |= dmc.irqPending ? 0x80 : 0;
|
||||
|
||||
frame.irqPending = false;
|
||||
setIRQ();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto APU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
const uint n = (addr >> 2) & 1; //pulse#
|
||||
|
||||
switch(addr) {
|
||||
|
||||
case 0x4000: case 0x4004: {
|
||||
pulse[n].duty = data >> 6;
|
||||
pulse[n].envelope.loopMode = data & 0x20;
|
||||
pulse[n].envelope.useSpeedAsVolume = data & 0x10;
|
||||
pulse[n].envelope.speed = data & 0x0f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4001: case 0x4005: {
|
||||
pulse[n].sweep.enable = data & 0x80;
|
||||
pulse[n].sweep.period = (data & 0x70) >> 4;
|
||||
pulse[n].sweep.decrement = data & 0x08;
|
||||
pulse[n].sweep.shift = data & 0x07;
|
||||
pulse[n].sweep.reload = true;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4002: case 0x4006: {
|
||||
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
|
||||
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x0700) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4003: case 0x4007: {
|
||||
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
|
||||
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x00ff) | (data << 8);
|
||||
|
||||
pulse[n].dutyCounter = 0;
|
||||
pulse[n].envelope.reloadDecay = true;
|
||||
|
||||
if(enabledChannels & (1 << n)) {
|
||||
pulse[n].lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4008: {
|
||||
triangle.haltLengthCounter = data & 0x80;
|
||||
triangle.linearLength = data & 0x7f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400a: {
|
||||
triangle.period = (triangle.period & 0x0700) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400b: {
|
||||
triangle.period = (triangle.period & 0x00ff) | (data << 8);
|
||||
|
||||
triangle.reloadLinear = true;
|
||||
|
||||
if(enabledChannels & (1 << 2)) {
|
||||
triangle.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400c: {
|
||||
noise.envelope.loopMode = data & 0x20;
|
||||
noise.envelope.useSpeedAsVolume = data & 0x10;
|
||||
noise.envelope.speed = data & 0x0f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400e: {
|
||||
noise.shortMode = data & 0x80;
|
||||
noise.period = data & 0x0f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400f: {
|
||||
noise.envelope.reloadDecay = true;
|
||||
|
||||
if(enabledChannels & (1 << 3)) {
|
||||
noise.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4010: {
|
||||
dmc.irqEnable = data & 0x80;
|
||||
dmc.loopMode = data & 0x40;
|
||||
dmc.period = data & 0x0f;
|
||||
|
||||
dmc.irqPending = dmc.irqPending && dmc.irqEnable && !dmc.loopMode;
|
||||
setIRQ();
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4011: {
|
||||
dmc.dacLatch = data & 0x7f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4012: {
|
||||
dmc.addrLatch = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4013: {
|
||||
dmc.lengthLatch = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4015: {
|
||||
if((data & 0x01) == 0) pulse[0].lengthCounter = 0;
|
||||
if((data & 0x02) == 0) pulse[1].lengthCounter = 0;
|
||||
if((data & 0x04) == 0) triangle.lengthCounter = 0;
|
||||
if((data & 0x08) == 0) noise.lengthCounter = 0;
|
||||
|
||||
(data & 0x10) ? dmc.start() : dmc.stop();
|
||||
dmc.irqPending = false;
|
||||
|
||||
setIRQ();
|
||||
enabledChannels = data & 0x1f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4017: {
|
||||
frame.mode = data >> 6;
|
||||
|
||||
frame.counter = 0;
|
||||
if(frame.mode & 2) clockFrameCounter();
|
||||
if(frame.mode & 1) {
|
||||
frame.irqPending = false;
|
||||
setIRQ();
|
||||
}
|
||||
frame.divider = FrameCounter::NtscPeriod;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::clockFrameCounter() -> void {
|
||||
frame.counter++;
|
||||
|
||||
if(frame.counter & 1) {
|
||||
pulse[0].clockLength();
|
||||
pulse[0].sweep.clock(0);
|
||||
pulse[1].clockLength();
|
||||
pulse[1].sweep.clock(1);
|
||||
triangle.clockLength();
|
||||
noise.clockLength();
|
||||
}
|
||||
|
||||
pulse[0].envelope.clock();
|
||||
pulse[1].envelope.clock();
|
||||
triangle.clockLinearLength();
|
||||
noise.envelope.clock();
|
||||
|
||||
if(frame.counter == 0) {
|
||||
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
|
||||
if(frame.mode == 0) {
|
||||
frame.irqPending = true;
|
||||
setIRQ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::clockFrameCounterDivider() -> void {
|
||||
frame.divider -= 2;
|
||||
if(frame.divider <= 0) {
|
||||
clockFrameCounter();
|
||||
frame.divider += FrameCounter::NtscPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8 APU::lengthCounterTable[32] = {
|
||||
0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e,
|
||||
0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e,
|
||||
};
|
||||
|
||||
const uint16 APU::noisePeriodTableNTSC[16] = {
|
||||
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
|
||||
};
|
||||
|
||||
const uint16 APU::noisePeriodTablePAL[16] = {
|
||||
4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
|
||||
};
|
||||
|
||||
const uint16 APU::dmcPeriodTableNTSC[16] = {
|
||||
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
|
||||
};
|
||||
|
||||
const uint16 APU::dmcPeriodTablePAL[16] = {
|
||||
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
struct APU : Thread {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
|
||||
|
||||
//apu.cpp
|
||||
APU();
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
auto setIRQ() -> void;
|
||||
auto setSample(int16 sample) -> void;
|
||||
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Envelope {
|
||||
auto volume() const -> uint;
|
||||
auto clock() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint4 speed;
|
||||
bool useSpeedAsVolume;
|
||||
bool loopMode;
|
||||
|
||||
bool reloadDecay;
|
||||
uint8 decayCounter;
|
||||
uint4 decayVolume;
|
||||
};
|
||||
|
||||
struct Sweep {
|
||||
auto checkPeriod() -> bool;
|
||||
auto clock(uint channel) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint8 shift;
|
||||
bool decrement;
|
||||
uint3 period;
|
||||
uint8 counter;
|
||||
bool enable;
|
||||
bool reload;
|
||||
uint11 pulsePeriod;
|
||||
};
|
||||
|
||||
struct Pulse {
|
||||
auto clockLength() -> void;
|
||||
auto checkPeriod() -> bool;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint lengthCounter;
|
||||
|
||||
Envelope envelope;
|
||||
Sweep sweep;
|
||||
|
||||
uint2 duty;
|
||||
uint3 dutyCounter;
|
||||
|
||||
uint11 period;
|
||||
uint periodCounter;
|
||||
} pulse[2];
|
||||
|
||||
struct Triangle {
|
||||
auto clockLength() -> void;
|
||||
auto clockLinearLength() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint lengthCounter;
|
||||
|
||||
uint8 linearLength;
|
||||
bool haltLengthCounter;
|
||||
|
||||
uint11 period;
|
||||
uint periodCounter;
|
||||
|
||||
uint5 stepCounter;
|
||||
uint8 linearLengthCounter;
|
||||
bool reloadLinear;
|
||||
} triangle;
|
||||
|
||||
struct Noise {
|
||||
auto clockLength() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint lengthCounter;
|
||||
|
||||
Envelope envelope;
|
||||
|
||||
uint4 period;
|
||||
uint periodCounter;
|
||||
|
||||
bool shortMode;
|
||||
uint15 lfsr;
|
||||
} noise;
|
||||
|
||||
struct DMC {
|
||||
auto start() -> void;
|
||||
auto stop() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint lengthCounter;
|
||||
bool irqPending;
|
||||
|
||||
uint4 period;
|
||||
uint periodCounter;
|
||||
|
||||
bool irqEnable;
|
||||
bool loopMode;
|
||||
|
||||
uint8 dacLatch;
|
||||
uint8 addrLatch;
|
||||
uint8 lengthLatch;
|
||||
|
||||
uint15 readAddr;
|
||||
uint dmaDelayCounter;
|
||||
|
||||
uint3 bitCounter;
|
||||
bool dmaBufferValid;
|
||||
uint8 dmaBuffer;
|
||||
|
||||
bool sampleValid;
|
||||
uint8 sample;
|
||||
} dmc;
|
||||
|
||||
struct FrameCounter {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
enum : uint { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
|
||||
|
||||
bool irqPending;
|
||||
|
||||
uint2 mode;
|
||||
uint2 counter;
|
||||
int divider;
|
||||
};
|
||||
|
||||
auto clockFrameCounter() -> void;
|
||||
auto clockFrameCounterDivider() -> void;
|
||||
|
||||
FrameCounter frame;
|
||||
|
||||
uint8 enabledChannels;
|
||||
int16 cartridgeSample;
|
||||
|
||||
int16 pulseDAC[32];
|
||||
int16 dmcTriangleNoiseDAC[128][16][16];
|
||||
|
||||
static const uint8 lengthCounterTable[32];
|
||||
static const uint16 dmcPeriodTableNTSC[16];
|
||||
static const uint16 dmcPeriodTablePAL[16];
|
||||
static const uint16 noisePeriodTableNTSC[16];
|
||||
static const uint16 noisePeriodTablePAL[16];
|
||||
};
|
||||
|
||||
extern APU apu;
|
||||
@@ -1,89 +0,0 @@
|
||||
auto APU::DMC::start() -> void {
|
||||
if(lengthCounter == 0) {
|
||||
readAddr = 0x4000 + (addrLatch << 6);
|
||||
lengthCounter = (lengthLatch << 4) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::DMC::stop() -> void {
|
||||
lengthCounter = 0;
|
||||
dmaDelayCounter = 0;
|
||||
cpu.rdyLine(1);
|
||||
cpu.rdyAddr(false);
|
||||
}
|
||||
|
||||
auto APU::DMC::clock() -> uint8 {
|
||||
uint8 result = dacLatch;
|
||||
|
||||
if(dmaDelayCounter > 0) {
|
||||
dmaDelayCounter--;
|
||||
|
||||
if(dmaDelayCounter == 1) {
|
||||
cpu.rdyAddr(true, 0x8000 | readAddr);
|
||||
} else if(dmaDelayCounter == 0) {
|
||||
cpu.rdyLine(1);
|
||||
cpu.rdyAddr(false);
|
||||
|
||||
dmaBuffer = cpu.mdr();
|
||||
dmaBufferValid = true;
|
||||
lengthCounter--;
|
||||
readAddr++;
|
||||
|
||||
if(lengthCounter == 0) {
|
||||
if(loopMode) {
|
||||
start();
|
||||
} else if(irqEnable) {
|
||||
irqPending = true;
|
||||
apu.setIRQ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
if(sampleValid) {
|
||||
int delta = (((sample >> bitCounter) & 1) << 2) - 2;
|
||||
uint data = dacLatch + delta;
|
||||
if((data & 0x80) == 0) dacLatch = data;
|
||||
}
|
||||
|
||||
if(++bitCounter == 0) {
|
||||
if(dmaBufferValid) {
|
||||
sampleValid = true;
|
||||
sample = dmaBuffer;
|
||||
dmaBufferValid = false;
|
||||
} else {
|
||||
sampleValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
periodCounter = Region::PAL() ? dmcPeriodTablePAL[period] : dmcPeriodTableNTSC[period];
|
||||
}
|
||||
|
||||
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
|
||||
cpu.rdyLine(0);
|
||||
dmaDelayCounter = 4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::DMC::power() -> void {
|
||||
lengthCounter = 0;
|
||||
irqPending = 0;
|
||||
|
||||
period = 0;
|
||||
periodCounter = Region::PAL() ? dmcPeriodTablePAL[0] : dmcPeriodTableNTSC[0];
|
||||
irqEnable = 0;
|
||||
loopMode = 0;
|
||||
dacLatch = 0;
|
||||
addrLatch = 0;
|
||||
lengthLatch = 0;
|
||||
readAddr = 0;
|
||||
dmaDelayCounter = 0;
|
||||
bitCounter = 0;
|
||||
dmaBufferValid = 0;
|
||||
dmaBuffer = 0;
|
||||
sampleValid = 0;
|
||||
sample = 0;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
auto APU::Envelope::volume() const -> uint {
|
||||
return useSpeedAsVolume ? speed : decayVolume;
|
||||
}
|
||||
|
||||
auto APU::Envelope::clock() -> void {
|
||||
if(reloadDecay) {
|
||||
reloadDecay = false;
|
||||
decayVolume = 0x0f;
|
||||
decayCounter = speed + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if(--decayCounter == 0) {
|
||||
decayCounter = speed + 1;
|
||||
if(decayVolume || loopMode) decayVolume--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Envelope::power() -> void {
|
||||
speed = 0;
|
||||
useSpeedAsVolume = 0;
|
||||
loopMode = 0;
|
||||
reloadDecay = 0;
|
||||
decayCounter = 0;
|
||||
decayVolume = 0;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
auto APU::Noise::clockLength() -> void {
|
||||
if(envelope.loopMode == 0) {
|
||||
if(lengthCounter > 0) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::clock() -> uint8 {
|
||||
if(lengthCounter == 0) return 0;
|
||||
|
||||
uint8 result = (lfsr & 1) ? envelope.volume() : 0;
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
uint feedback;
|
||||
|
||||
if(shortMode) {
|
||||
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1);
|
||||
} else {
|
||||
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1);
|
||||
}
|
||||
|
||||
lfsr = (lfsr >> 1) | (feedback << 14);
|
||||
periodCounter = Region::PAL() ? apu.noisePeriodTablePAL[period] : apu.noisePeriodTableNTSC[period];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Noise::power() -> void {
|
||||
lengthCounter = 0;
|
||||
|
||||
envelope.speed = 0;
|
||||
envelope.useSpeedAsVolume = 0;
|
||||
envelope.loopMode = 0;
|
||||
envelope.reloadDecay = 0;
|
||||
envelope.decayCounter = 0;
|
||||
envelope.decayVolume = 0;
|
||||
|
||||
period = 0;
|
||||
periodCounter = 1;
|
||||
shortMode = 0;
|
||||
lfsr = 1;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
auto APU::Pulse::clockLength() -> void {
|
||||
if(envelope.loopMode == 0) {
|
||||
if(lengthCounter) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Pulse::clock() -> uint8 {
|
||||
if(!sweep.checkPeriod()) return 0;
|
||||
if(lengthCounter == 0) return 0;
|
||||
|
||||
static const uint dutyTable[4][8] = {
|
||||
{0, 0, 0, 0, 0, 0, 0, 1}, //12.5%
|
||||
{0, 0, 0, 0, 0, 0, 1, 1}, //25.0%
|
||||
{0, 0, 0, 0, 1, 1, 1, 1}, //50.0%
|
||||
{1, 1, 1, 1, 1, 1, 0, 0}, //25.0% (negated)
|
||||
};
|
||||
uint8 result = dutyTable[duty][dutyCounter] ? envelope.volume() : 0;
|
||||
if(sweep.pulsePeriod < 0x008) result = 0;
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
periodCounter = (sweep.pulsePeriod + 1) * 2;
|
||||
dutyCounter--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Pulse::power() -> void {
|
||||
envelope.power();
|
||||
sweep.power();
|
||||
|
||||
lengthCounter = 0;
|
||||
|
||||
duty = 0;
|
||||
dutyCounter = 0;
|
||||
period = 0;
|
||||
periodCounter = 1;
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
auto APU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
pulse[0].serialize(s);
|
||||
pulse[1].serialize(s);
|
||||
triangle.serialize(s);
|
||||
dmc.serialize(s);
|
||||
frame.serialize(s);
|
||||
|
||||
s.integer(enabledChannels);
|
||||
s.integer(cartridgeSample);
|
||||
}
|
||||
|
||||
auto APU::Envelope::serialize(serializer& s) -> void {
|
||||
s.integer(speed);
|
||||
s.integer(useSpeedAsVolume);
|
||||
s.integer(loopMode);
|
||||
|
||||
s.integer(reloadDecay);
|
||||
s.integer(decayCounter);
|
||||
s.integer(decayVolume);
|
||||
}
|
||||
|
||||
auto APU::Sweep::serialize(serializer& s) -> void {
|
||||
s.integer(shift);
|
||||
s.integer(decrement);
|
||||
s.integer(period);
|
||||
s.integer(counter);
|
||||
s.integer(enable);
|
||||
s.integer(reload);
|
||||
s.integer(pulsePeriod);
|
||||
}
|
||||
|
||||
auto APU::Pulse::serialize(serializer& s) -> void {
|
||||
s.integer(lengthCounter);
|
||||
|
||||
envelope.serialize(s);
|
||||
sweep.serialize(s);
|
||||
|
||||
s.integer(duty);
|
||||
s.integer(dutyCounter);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(periodCounter);
|
||||
}
|
||||
|
||||
auto APU::Triangle::serialize(serializer& s) -> void {
|
||||
s.integer(lengthCounter);
|
||||
|
||||
s.integer(linearLength);
|
||||
s.integer(haltLengthCounter);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(periodCounter);
|
||||
|
||||
s.integer(stepCounter);
|
||||
s.integer(linearLengthCounter);
|
||||
s.integer(reloadLinear);
|
||||
}
|
||||
|
||||
auto APU::Noise::serialize(serializer& s) -> void {
|
||||
s.integer(lengthCounter);
|
||||
|
||||
envelope.serialize(s);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(periodCounter);
|
||||
|
||||
s.integer(shortMode);
|
||||
s.integer(lfsr);
|
||||
}
|
||||
|
||||
auto APU::DMC::serialize(serializer& s) -> void {
|
||||
s.integer(lengthCounter);
|
||||
s.integer(irqPending);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(periodCounter);
|
||||
|
||||
s.integer(irqEnable);
|
||||
s.integer(loopMode);
|
||||
|
||||
s.integer(dacLatch);
|
||||
s.integer(addrLatch);
|
||||
s.integer(lengthLatch);
|
||||
|
||||
s.integer(readAddr);
|
||||
s.integer(dmaDelayCounter);
|
||||
|
||||
s.integer(bitCounter);
|
||||
s.integer(dmaBufferValid);
|
||||
s.integer(dmaBuffer);
|
||||
|
||||
s.integer(sampleValid);
|
||||
s.integer(sample);
|
||||
}
|
||||
|
||||
auto APU::FrameCounter::serialize(serializer& s) -> void {
|
||||
s.integer(irqPending);
|
||||
|
||||
s.integer(mode);
|
||||
s.integer(counter);
|
||||
s.integer(divider);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
auto APU::Sweep::checkPeriod() -> bool {
|
||||
if(pulsePeriod > 0x7ff) return false;
|
||||
|
||||
if(decrement == 0) {
|
||||
if((pulsePeriod + (pulsePeriod >> shift)) & 0x800) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto APU::Sweep::clock(uint channel) -> void {
|
||||
if(--counter == 0) {
|
||||
counter = period + 1;
|
||||
if(enable && shift && pulsePeriod > 8) {
|
||||
int delta = pulsePeriod >> shift;
|
||||
|
||||
if(decrement) {
|
||||
pulsePeriod -= delta;
|
||||
if(channel == 0) pulsePeriod--;
|
||||
} else if((pulsePeriod + delta) < 0x800) {
|
||||
pulsePeriod += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(reload) {
|
||||
reload = false;
|
||||
counter = period + 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Sweep::power() -> void {
|
||||
shift = 0;
|
||||
decrement = 0;
|
||||
period = 0;
|
||||
counter = 1;
|
||||
enable = 0;
|
||||
reload = 0;
|
||||
pulsePeriod = 0;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
auto APU::Triangle::clockLength() -> void {
|
||||
if(haltLengthCounter == 0) {
|
||||
if(lengthCounter > 0) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Triangle::clockLinearLength() -> void {
|
||||
if(reloadLinear) {
|
||||
linearLengthCounter = linearLength;
|
||||
} else if(linearLengthCounter) {
|
||||
linearLengthCounter--;
|
||||
}
|
||||
|
||||
if(haltLengthCounter == 0) reloadLinear = false;
|
||||
}
|
||||
|
||||
auto APU::Triangle::clock() -> uint8 {
|
||||
uint8 result = stepCounter & 0x0f;
|
||||
if((stepCounter & 0x10) == 0) result ^= 0x0f;
|
||||
if(lengthCounter == 0 || linearLengthCounter == 0) return result;
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
stepCounter++;
|
||||
periodCounter = period + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Triangle::power() -> void {
|
||||
lengthCounter = 0;
|
||||
|
||||
linearLength = 0;
|
||||
haltLengthCounter = 0;
|
||||
period = 0;
|
||||
periodCounter = 1;
|
||||
stepCounter = 0;
|
||||
linearLengthCounter = 0;
|
||||
reloadLinear = 0;
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
//BANDAI-FCG
|
||||
|
||||
struct BandaiFCG : Board {
|
||||
BandaiFCG(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqCounterEnable) {
|
||||
if(--irqCounter == 0xffff) {
|
||||
cpu.irqLine(1);
|
||||
irqCounterEnable = false;
|
||||
}
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
case 2: return 0x0000 | (addr & 0x03ff);
|
||||
case 3: return 0x0400 | (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (region == 0 ? prgBank : (uint8)0x0f);
|
||||
return prgrom.read((bank << 14) | (addr & 0x3fff));
|
||||
}
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr >= 0x6000) {
|
||||
switch(addr & 15) {
|
||||
case 0x00: case 0x01: case 0x02: case 0x03:
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
chrBank[addr & 7] = data;
|
||||
break;
|
||||
case 0x08:
|
||||
prgBank = data & 0x0f;
|
||||
break;
|
||||
case 0x09:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
case 0x0a:
|
||||
cpu.irqLine(0);
|
||||
irqCounterEnable = data & 0x01;
|
||||
irqCounter = irqLatch;
|
||||
break;
|
||||
case 0x0b:
|
||||
irqLatch = (irqLatch & 0xff00) | (data << 0);
|
||||
break;
|
||||
case 0x0c:
|
||||
irqLatch = (irqLatch & 0x00ff) | (data << 8);
|
||||
break;
|
||||
case 0x0d:
|
||||
//todo: serial EEPROM support
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
addr = (chrBank[addr >> 10] << 10) | (addr & 0x03ff);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
addr = (chrBank[addr >> 10] << 10) | (addr & 0x03ff);
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : chrBank) n = 0;
|
||||
prgBank = 0;
|
||||
mirror = 0;
|
||||
irqCounterEnable = 0;
|
||||
irqCounter = 0;
|
||||
irqLatch = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.array(chrBank);
|
||||
s.integer(prgBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irqCounterEnable);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqLatch);
|
||||
}
|
||||
|
||||
uint8 chrBank[8];
|
||||
uint8 prgBank;
|
||||
uint2 mirror;
|
||||
bool irqCounterEnable;
|
||||
uint16 irqCounter;
|
||||
uint16 irqLatch;
|
||||
};
|
||||
@@ -1,229 +0,0 @@
|
||||
#include "bandai-fcg.cpp"
|
||||
#include "konami-vrc1.cpp"
|
||||
#include "konami-vrc2.cpp"
|
||||
#include "konami-vrc3.cpp"
|
||||
#include "konami-vrc4.cpp"
|
||||
#include "konami-vrc6.cpp"
|
||||
#include "konami-vrc7.cpp"
|
||||
#include "nes-axrom.cpp"
|
||||
#include "nes-bnrom.cpp"
|
||||
#include "nes-cnrom.cpp"
|
||||
#include "nes-exrom.cpp"
|
||||
#include "nes-fxrom.cpp"
|
||||
#include "nes-gxrom.cpp"
|
||||
#include "nes-hkrom.cpp"
|
||||
#include "nes-nrom.cpp"
|
||||
#include "nes-pxrom.cpp"
|
||||
#include "nes-sxrom.cpp"
|
||||
#include "nes-txrom.cpp"
|
||||
#include "nes-uxrom.cpp"
|
||||
#include "sunsoft-5b.cpp"
|
||||
|
||||
Board::Board(Markup::Node& document) {
|
||||
cartridge.board = this;
|
||||
auto board = document["board"];
|
||||
|
||||
information.type = board["id"].text();
|
||||
information.battery = (bool)board["prg/ram/name"];
|
||||
|
||||
auto prom = board["prg/rom"];
|
||||
auto pram = board["prg/ram"];
|
||||
auto crom = board["chr/rom"];
|
||||
auto cram = board["chr/ram"];
|
||||
|
||||
prgrom.size = prom["size"].natural();
|
||||
prgram.size = pram["size"].natural();
|
||||
chrrom.size = crom["size"].natural();
|
||||
chrram.size = cram["size"].natural();
|
||||
|
||||
if(prgrom.size) prgrom.data = new uint8_t[prgrom.size]();
|
||||
if(prgram.size) prgram.data = new uint8_t[prgram.size]();
|
||||
if(chrrom.size) chrrom.data = new uint8_t[chrrom.size]();
|
||||
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
|
||||
|
||||
if(prgrom.name = prom["name"].text()) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), prgrom.name, File::Read, File::Required)) {
|
||||
fp->read(prgrom.data, min(prgrom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(prgram.name = pram["name"].text()) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), prgram.name, File::Read)) {
|
||||
fp->read(prgram.data, min(prgram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(chrrom.name = crom["name"].text()) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), chrrom.name, File::Read, File::Required)) {
|
||||
fp->read(chrrom.data, min(chrrom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(chrram.name = cram["name"].text()) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), chrram.name, File::Read)) {
|
||||
fp->read(chrram.data, min(chrram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
|
||||
prgram.writable = true;
|
||||
chrram.writable = true;
|
||||
}
|
||||
|
||||
auto Board::save() -> void {
|
||||
auto document = BML::unserialize(cartridge.manifest());
|
||||
|
||||
if(auto name = document["board/prg/ram/name"].text()) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), name, File::Write)) {
|
||||
fp->write(prgram.data, prgram.size);
|
||||
}
|
||||
}
|
||||
|
||||
if(auto name = document["board/chr/ram/name"].text()) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), name, File::Write)) {
|
||||
fp->write(chrram.data, chrram.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Board::Memory::read(uint addr) const -> uint8 {
|
||||
return data[mirror(addr, size)];
|
||||
}
|
||||
|
||||
auto Board::Memory::write(uint addr, uint8 byte) -> void {
|
||||
if(writable) data[mirror(addr, size)] = byte;
|
||||
}
|
||||
|
||||
auto Board::mirror(uint addr, uint size) -> uint {
|
||||
uint base = 0;
|
||||
if(size) {
|
||||
uint mask = 1 << 23;
|
||||
while(addr >= size) {
|
||||
while(!(addr & mask)) mask >>= 1;
|
||||
addr -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
base += addr;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
auto Board::main() -> void {
|
||||
cartridge.step(cartridge.rate() * 4095);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto Board::tick() -> void {
|
||||
cartridge.step(cartridge.rate());
|
||||
cartridge.synchronize(cpu);
|
||||
}
|
||||
|
||||
auto Board::readCHR(uint addr) -> uint8 {
|
||||
if(chrram.size) return chrram.data[mirror(addr, chrram.size)];
|
||||
if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)];
|
||||
return 0u;
|
||||
}
|
||||
|
||||
auto Board::writeCHR(uint addr, uint8 data) -> void {
|
||||
if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data;
|
||||
}
|
||||
|
||||
auto Board::power() -> void {
|
||||
}
|
||||
|
||||
auto Board::serialize(serializer& s) -> void {
|
||||
if(prgram.size) s.array(prgram.data, prgram.size);
|
||||
if(chrram.size) s.array(chrram.data, chrram.size);
|
||||
}
|
||||
|
||||
auto Board::load(string manifest) -> Board* {
|
||||
auto document = BML::unserialize(manifest);
|
||||
cartridge.information.title = document["information/title"].text();
|
||||
|
||||
string type = document["board/id"].text();
|
||||
|
||||
if(type == "BANDAI-FCG" ) return new BandaiFCG(document);
|
||||
|
||||
if(type == "KONAMI-VRC-1") return new KonamiVRC1(document);
|
||||
if(type == "KONAMI-VRC-2") return new KonamiVRC2(document);
|
||||
if(type == "KONAMI-VRC-3") return new KonamiVRC3(document);
|
||||
if(type == "KONAMI-VRC-4") return new KonamiVRC4(document);
|
||||
if(type == "KONAMI-VRC-6") return new KonamiVRC6(document);
|
||||
if(type == "KONAMI-VRC-7") return new KonamiVRC7(document);
|
||||
|
||||
if(type == "NES-AMROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-ANROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-AN1ROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-AOROM" ) return new NES_AxROM(document);
|
||||
|
||||
if(type == "NES-BNROM" ) return new NES_BNROM(document);
|
||||
|
||||
if(type == "NES-CNROM" ) return new NES_CNROM(document);
|
||||
|
||||
if(type == "NES-EKROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-ELROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-ETROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-EWROM" ) return new NES_ExROM(document);
|
||||
|
||||
if(type == "NES-FJROM" ) return new NES_FxROM(document);
|
||||
if(type == "NES-FKROM" ) return new NES_FxROM(document);
|
||||
|
||||
if(type == "NES-GNROM" ) return new NES_GxROM(document);
|
||||
if(type == "NES-MHROM" ) return new NES_GxROM(document);
|
||||
|
||||
if(type == "NES-HKROM" ) return new NES_HKROM(document);
|
||||
|
||||
if(type == "NES-NROM" ) return new NES_NROM(document);
|
||||
if(type == "NES-NROM-128") return new NES_NROM(document);
|
||||
if(type == "NES-NROM-256") return new NES_NROM(document);
|
||||
|
||||
if(type == "NES-PEEOROM" ) return new NES_PxROM(document);
|
||||
if(type == "NES-PNROM" ) return new NES_PxROM(document);
|
||||
|
||||
if(type == "NES-SAROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SBROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SCROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SC1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SEROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SFROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SFEXPROM") return new NES_SxROM(document);
|
||||
if(type == "NES-SGROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SHROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SH1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SIROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SJROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SKROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SLROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL2ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL3ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SLRROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SMROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SNROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SOROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SUROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SXROM" ) return new NES_SxROM(document);
|
||||
|
||||
if(type == "NES-TBROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TEROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TFROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TGROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TKROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TKSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TLROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TL1ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TL2ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TLSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TNROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TQROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TR1ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TVROM" ) return new NES_TxROM(document);
|
||||
|
||||
if(type == "NES-UNROM" ) return new NES_UxROM(document);
|
||||
if(type == "NES-UOROM" ) return new NES_UxROM(document);
|
||||
|
||||
if(type == "SUNSOFT-5B" ) return new Sunsoft5B(document);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
struct Board {
|
||||
struct Memory {
|
||||
inline Memory(uint8_t* data, uint size) : data(data), size(size) {}
|
||||
inline Memory() : data(nullptr), size(0u), writable(false) {}
|
||||
inline ~Memory() { if(data) delete[] data; }
|
||||
|
||||
inline auto read(uint addr) const -> uint8;
|
||||
inline auto write(uint addr, uint8 data) -> void;
|
||||
|
||||
string name;
|
||||
uint8_t* data = nullptr;
|
||||
uint size = 0;
|
||||
bool writable = false;
|
||||
};
|
||||
|
||||
virtual ~Board() = default;
|
||||
|
||||
static auto mirror(uint addr, uint size) -> uint;
|
||||
|
||||
Board(Markup::Node& document);
|
||||
auto save() -> void;
|
||||
|
||||
virtual auto main() -> void;
|
||||
virtual auto tick() -> void;
|
||||
|
||||
virtual auto readPRG(uint addr) -> uint8 = 0;
|
||||
virtual auto writePRG(uint addr, uint8 data) -> void = 0;
|
||||
|
||||
virtual auto readCHR(uint addr) -> uint8;
|
||||
virtual auto writeCHR(uint addr, uint8 data) -> void;
|
||||
|
||||
virtual inline auto scanline(uint y) -> void {}
|
||||
|
||||
virtual auto power() -> void;
|
||||
|
||||
virtual auto serialize(serializer&) -> void;
|
||||
|
||||
static auto load(string manifest) -> Board*;
|
||||
|
||||
struct Information {
|
||||
string type;
|
||||
bool battery;
|
||||
} information;
|
||||
|
||||
Memory prgrom;
|
||||
Memory prgram;
|
||||
Memory chrrom;
|
||||
Memory chrram;
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
struct KonamiVRC1 : Board {
|
||||
KonamiVRC1(Markup::Node& document) : Board(document), vrc1(*this) {
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(vrc1.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) return vrc1.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc1.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc1.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc1.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(vrc1.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc1.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc1.serialize(s);
|
||||
}
|
||||
|
||||
VRC1 vrc1;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
struct KonamiVRC2 : Board {
|
||||
KonamiVRC2(Markup::Node& document) : Board(document), vrc2(*this) {
|
||||
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
|
||||
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return vrc2.readRAM(addr);
|
||||
return prgrom.read(vrc2.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return vrc2.writeRAM(addr, data);
|
||||
|
||||
bool a0 = (addr & settings.pinout.a0);
|
||||
bool a1 = (addr & settings.pinout.a1);
|
||||
addr &= 0xfff0;
|
||||
addr |= (a0 << 0) | (a1 << 1);
|
||||
return vrc2.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc2.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc2.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc2.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(vrc2.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc2.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc2.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
struct Pinout {
|
||||
uint a0;
|
||||
uint a1;
|
||||
} pinout;
|
||||
} settings;
|
||||
|
||||
VRC2 vrc2;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
struct KonamiVRC3 : Board {
|
||||
KonamiVRC3(Markup::Node& document) : Board(document), vrc3(*this) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
vrc3.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) return prgram.read(addr & 0x1fff);
|
||||
if(addr & 0x8000) return prgrom.read(vrc3.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return prgram.write(addr & 0x1fff, data);
|
||||
if(addr & 0x8000) return vrc3.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
return chrram.read(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
return chrram.write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc3.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc3.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
VRC3 vrc3;
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
struct KonamiVRC4 : Board {
|
||||
KonamiVRC4(Markup::Node& document) : Board(document), vrc4(*this) {
|
||||
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
|
||||
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return vrc4.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
return prgrom.read(vrc4.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
bool a0 = (addr & settings.pinout.a0);
|
||||
bool a1 = (addr & settings.pinout.a1);
|
||||
addr &= 0xfff0;
|
||||
addr |= (a1 << 1) | (a0 << 0);
|
||||
return vrc4.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc4.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc4.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc4.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(vrc4.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc4.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc4.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
struct Pinout {
|
||||
uint a0;
|
||||
uint a1;
|
||||
} pinout;
|
||||
} settings;
|
||||
|
||||
VRC4 vrc4;
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
struct KonamiVRC6 : Board {
|
||||
KonamiVRC6(Markup::Node& document) : Board(document), vrc6(*this) {
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8{
|
||||
if((addr & 0xe000) == 0x6000) return vrc6.readRAM(addr);
|
||||
if(addr & 0x8000) return prgrom.read(vrc6.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return vrc6.writeRAM(addr, data);
|
||||
if(addr & 0x8000) {
|
||||
addr = (addr & 0xf003);
|
||||
if(prgram.size) addr = (addr & ~3) | ((addr & 2) >> 1) | ((addr & 1) << 1);
|
||||
return vrc6.writeIO(addr, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc6.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc6.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc6.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(vrc6.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc6.serialize(s);
|
||||
}
|
||||
|
||||
auto main() -> void { vrc6.main(); }
|
||||
auto power() -> void { vrc6.power(); }
|
||||
|
||||
VRC6 vrc6;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
struct KonamiVRC7 : Board {
|
||||
KonamiVRC7(Markup::Node& document) : Board(document), vrc7(*this) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return vrc7.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
return prgrom.read(vrc7.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
return vrc7.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc7.addrCIRAM(addr));
|
||||
return chrram.read(vrc7.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc7.addrCIRAM(addr), data);
|
||||
return chrram.write(vrc7.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc7.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc7.serialize(s);
|
||||
}
|
||||
|
||||
VRC7 vrc7;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
//NES-AMROM
|
||||
//NES-ANROM
|
||||
//NES-AN1ROM
|
||||
//NES-AOROM
|
||||
|
||||
struct NES_AxROM : Board {
|
||||
NES_AxROM(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) {
|
||||
prgBank = data & 0x0f;
|
||||
mirrorSelect = data & 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM((mirrorSelect << 10) | (addr & 0x03ff));
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM((mirrorSelect << 10) | (addr & 0x03ff), data);
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgBank = 0x0f;
|
||||
mirrorSelect = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prgBank);
|
||||
s.integer(mirrorSelect);
|
||||
}
|
||||
|
||||
uint4 prgBank;
|
||||
bool mirrorSelect;
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
//NES-BN-ROM-01
|
||||
|
||||
struct NES_BNROM : Board {
|
||||
NES_BNROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prgBank = data & 0x03;
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr);
|
||||
}
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr, data);
|
||||
}
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 prgBank;
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
//NES-CNROM
|
||||
|
||||
struct NES_CNROM : Board {
|
||||
NES_CNROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(addr & 0x7fff);
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) chrBank = data & 0x03;
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
chrBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(chrBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 chrBank;
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
struct NES_ExROM : Board {
|
||||
NES_ExROM(Markup::Node& document) : Board(document), mmc5(*this) {
|
||||
revision = Revision::ELROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc5.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
return mmc5.readPRG(addr);
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
mmc5.writePRG(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
return mmc5.readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
mmc5.writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
mmc5.scanline(y);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc5.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc5.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
EKROM,
|
||||
ELROM,
|
||||
ETROM,
|
||||
EWROM,
|
||||
} revision;
|
||||
|
||||
MMC5 mmc5;
|
||||
};
|
||||
@@ -1,86 +0,0 @@
|
||||
//MMC4
|
||||
|
||||
struct NES_FxROM : Board {
|
||||
NES_FxROM(Markup::Node& document) : Board(document) {
|
||||
revision = Revision::FKROM;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
uint bank = addr < 0xc000 ? prgBank : (uint4)0x0f;
|
||||
return prgrom.read((bank * 0x4000) | (addr & 0x3fff));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
switch(addr & 0xf000) {
|
||||
case 0xa000: prgBank = data & 0x0f; break;
|
||||
case 0xb000: chrBank[0][0] = data & 0x1f; break;
|
||||
case 0xc000: chrBank[0][1] = data & 0x1f; break;
|
||||
case 0xd000: chrBank[1][0] = data & 0x1f; break;
|
||||
case 0xe000: chrBank[1][1] = data & 0x1f; break;
|
||||
case 0xf000: mirror = data & 0x01; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgBank = 0;
|
||||
chrBank[0][0] = 0;
|
||||
chrBank[0][1] = 0;
|
||||
chrBank[1][0] = 0;
|
||||
chrBank[1][1] = 0;
|
||||
mirror = 0;
|
||||
latch[0] = 0;
|
||||
latch[1] = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prgBank);
|
||||
s.integer(chrBank[0][0]);
|
||||
s.integer(chrBank[0][1]);
|
||||
s.integer(chrBank[1][0]);
|
||||
s.integer(chrBank[1][1]);
|
||||
s.integer(mirror);
|
||||
s.array(latch);
|
||||
}
|
||||
|
||||
enum Revision : uint {
|
||||
FJROM,
|
||||
FKROM,
|
||||
} revision;
|
||||
|
||||
uint4 prgBank;
|
||||
uint5 chrBank[2][2];
|
||||
bool mirror;
|
||||
bool latch[2];
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
//NES-GNROM
|
||||
//NES-MHROM
|
||||
|
||||
struct NES_GxROM : Board {
|
||||
NES_GxROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) {
|
||||
prgBank = (data & 0x30) >> 4;
|
||||
chrBank = (data & 0x03) >> 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgBank = 0;
|
||||
chrBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(prgBank);
|
||||
s.integer(chrBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 prgBank;
|
||||
uint2 chrBank;
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
struct NES_HKROM : Board {
|
||||
NES_HKROM(Markup::Node& document) : Board(document), mmc6(*this) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc6.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xf000) == 0x7000) return mmc6.readRAM(addr);
|
||||
if(addr & 0x8000) return prgrom.read(mmc6.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xf000) == 0x7000) return mmc6.writeRAM(addr, data);
|
||||
if(addr & 0x8000) return mmc6.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
mmc6.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc6.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc6.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
mmc6.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(mmc6.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(mmc6.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc6.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc6.serialize(s);
|
||||
}
|
||||
|
||||
MMC6 mmc6;
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
//NES-NROM-128
|
||||
//NES-NROM-256
|
||||
|
||||
struct NES_NROM : Board {
|
||||
NES_NROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(addr);
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
if(chrram.size) return chrram.read(addr);
|
||||
return chrrom.read(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
if(chrram.size) return chrram.write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
//MMC2
|
||||
|
||||
struct NES_PxROM : Board {
|
||||
NES_PxROM(Markup::Node& document) : Board(document) {
|
||||
revision = Revision::PNROM;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
uint bank = 0;
|
||||
switch((addr / 0x2000) & 3) {
|
||||
case 0: bank = prgBank; break;
|
||||
case 1: bank = 0x0d; break;
|
||||
case 2: bank = 0x0e; break;
|
||||
case 3: bank = 0x0f; break;
|
||||
}
|
||||
return prgrom.read((bank * 0x2000) | (addr & 0x1fff));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
switch(addr & 0xf000) {
|
||||
case 0xa000: prgBank = data & 0x0f; break;
|
||||
case 0xb000: chrBank[0][0] = data & 0x1f; break;
|
||||
case 0xc000: chrBank[0][1] = data & 0x1f; break;
|
||||
case 0xd000: chrBank[1][0] = data & 0x1f; break;
|
||||
case 0xe000: chrBank[1][1] = data & 0x1f; break;
|
||||
case 0xf000: mirror = data & 0x01; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgBank = 0;
|
||||
chrBank[0][0] = 0;
|
||||
chrBank[0][1] = 0;
|
||||
chrBank[1][0] = 0;
|
||||
chrBank[1][1] = 0;
|
||||
mirror = 0;
|
||||
latch[0] = 0;
|
||||
latch[1] = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prgBank);
|
||||
s.integer(chrBank[0][0]);
|
||||
s.integer(chrBank[0][1]);
|
||||
s.integer(chrBank[1][0]);
|
||||
s.integer(chrBank[1][1]);
|
||||
s.integer(mirror);
|
||||
s.array(latch);
|
||||
}
|
||||
|
||||
enum Revision : uint {
|
||||
PEEOROM,
|
||||
PNROM,
|
||||
} revision;
|
||||
|
||||
uint4 prgBank;
|
||||
uint5 chrBank[2][2];
|
||||
bool mirror;
|
||||
bool latch[2];
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
struct NES_SxROM : Board {
|
||||
NES_SxROM(Markup::Node& document) : Board(document), mmc1(*this) {
|
||||
revision = Revision::SXROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return mmc1.main();
|
||||
}
|
||||
|
||||
auto addrRAM(uint addr) -> uint {
|
||||
uint bank = 0;
|
||||
if(revision == Revision::SOROM) bank = (mmc1.chrBank[0] & 0x08) >> 3;
|
||||
if(revision == Revision::SUROM) bank = (mmc1.chrBank[0] & 0x0c) >> 2;
|
||||
if(revision == Revision::SXROM) bank = (mmc1.chrBank[0] & 0x0c) >> 2;
|
||||
return (bank << 13) | (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
if(revision == Revision::SNROM) {
|
||||
if(mmc1.chrBank[0] & 0x10) return cpu.mdr();
|
||||
}
|
||||
if(mmc1.ramDisable) return 0x00;
|
||||
return prgram.read(addrRAM(addr));
|
||||
}
|
||||
|
||||
if(addr & 0x8000) {
|
||||
addr = mmc1.addrPRG(addr);
|
||||
if(revision == Revision::SXROM) {
|
||||
addr |= ((mmc1.chrBank[0] & 0x10) >> 4) << 18;
|
||||
}
|
||||
return prgrom.read(addr);
|
||||
}
|
||||
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
if(revision == Revision::SNROM) {
|
||||
if(mmc1.chrBank[0] & 0x10) return;
|
||||
}
|
||||
if(mmc1.ramDisable) return;
|
||||
return prgram.write(addrRAM(addr), data);
|
||||
}
|
||||
|
||||
if(addr & 0x8000) return mmc1.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc1.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc1.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(mmc1.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(mmc1.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc1.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc1.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
SAROM,
|
||||
SBROM,
|
||||
SCROM,
|
||||
SC1ROM,
|
||||
SEROM,
|
||||
SFROM,
|
||||
SGROM,
|
||||
SHROM,
|
||||
SH1ROM,
|
||||
SIROM,
|
||||
SJROM,
|
||||
SKROM,
|
||||
SLROM,
|
||||
SL1ROM,
|
||||
SL2ROM,
|
||||
SL3ROM,
|
||||
SLRROM,
|
||||
SMROM,
|
||||
SNROM,
|
||||
SOROM,
|
||||
SUROM,
|
||||
SXROM,
|
||||
} revision;
|
||||
|
||||
MMC1 mmc1;
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
struct NES_TxROM : Board {
|
||||
NES_TxROM(Markup::Node& document) : Board(document), mmc3(*this) {
|
||||
revision = Revision::TLROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc3.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) return mmc3.readRAM(addr);
|
||||
if(addr & 0x8000) return prgrom.read(mmc3.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return mmc3.writeRAM(addr, data);
|
||||
if(addr & 0x8000) return mmc3.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
mmc3.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc3.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc3.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
mmc3.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(mmc3.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(mmc3.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc3.power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc3.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
TBROM,
|
||||
TEROM,
|
||||
TFROM,
|
||||
TGROM,
|
||||
TKROM,
|
||||
TKSROM,
|
||||
TLROM,
|
||||
TL1ROM,
|
||||
TL2ROM,
|
||||
TLSROM,
|
||||
TNROM,
|
||||
TQROM,
|
||||
TR1ROM,
|
||||
TSROM,
|
||||
TVROM,
|
||||
} revision;
|
||||
|
||||
MMC3 mmc3;
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
//NES-UNROM
|
||||
//NES-UOROM
|
||||
|
||||
struct NES_UxROM : Board {
|
||||
NES_UxROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xc000) == 0x8000) return prgrom.read((prgBank << 14) | (addr & 0x3fff));
|
||||
if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prgBank = data & 0x0f;
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr);
|
||||
}
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr, data);
|
||||
}
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint4 prgBank;
|
||||
};
|
||||
@@ -1,216 +0,0 @@
|
||||
//SUNSOFT-5B
|
||||
|
||||
struct Sunsoft5B : Board {
|
||||
Sunsoft5B(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
struct Pulse {
|
||||
auto clock() -> void {
|
||||
if(--counter == 0) {
|
||||
counter = frequency << 4;
|
||||
duty ^= 1;
|
||||
}
|
||||
output = duty ? volume : (uint4)0;
|
||||
if(disable) output = 0;
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
disable = 1;
|
||||
frequency = 1;
|
||||
volume = 0;
|
||||
|
||||
counter = 0;
|
||||
duty = 0;
|
||||
output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(disable);
|
||||
s.integer(frequency);
|
||||
s.integer(volume);
|
||||
|
||||
s.integer(counter);
|
||||
s.integer(duty);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
bool disable;
|
||||
uint12 frequency;
|
||||
uint4 volume;
|
||||
|
||||
uint16 counter; //12-bit countdown + 4-bit phase
|
||||
uint1 duty;
|
||||
uint4 output;
|
||||
} pulse[3];
|
||||
|
||||
auto main() -> void {
|
||||
if(irqCounterEnable) {
|
||||
if(--irqCounter == 0xffff) {
|
||||
cpu.irqLine(irqEnable);
|
||||
}
|
||||
}
|
||||
|
||||
pulse[0].clock();
|
||||
pulse[1].clock();
|
||||
pulse[2].clock();
|
||||
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
||||
apu.setSample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
|
||||
uint8 bank = 0x3f; //((addr & 0xe000) == 0xe000
|
||||
if((addr & 0xe000) == 0x6000) bank = prgBank[0];
|
||||
if((addr & 0xe000) == 0x8000) bank = prgBank[1];
|
||||
if((addr & 0xe000) == 0xa000) bank = prgBank[2];
|
||||
if((addr & 0xe000) == 0xc000) bank = prgBank[3];
|
||||
|
||||
bool ramEnable = bank & 0x80;
|
||||
bool ramSelect = bank & 0x40;
|
||||
bank &= 0x3f;
|
||||
|
||||
if(ramSelect) {
|
||||
if(!ramEnable) return cpu.mdr();
|
||||
return prgram.data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
addr = (bank << 13) | (addr & 0x1fff);
|
||||
return prgrom.read(addr);
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
if(addr == 0x8000) {
|
||||
mmuPort = data & 0x0f;
|
||||
}
|
||||
|
||||
if(addr == 0xa000) {
|
||||
switch(mmuPort) {
|
||||
case 0: chrBank[0] = data; break;
|
||||
case 1: chrBank[1] = data; break;
|
||||
case 2: chrBank[2] = data; break;
|
||||
case 3: chrBank[3] = data; break;
|
||||
case 4: chrBank[4] = data; break;
|
||||
case 5: chrBank[5] = data; break;
|
||||
case 6: chrBank[6] = data; break;
|
||||
case 7: chrBank[7] = data; break;
|
||||
case 8: prgBank[0] = data; break;
|
||||
case 9: prgBank[1] = data; break;
|
||||
case 10: prgBank[2] = data; break;
|
||||
case 11: prgBank[3] = data; break;
|
||||
case 12: mirror = data & 3; break;
|
||||
case 13:
|
||||
irqEnable = data & 0x80;
|
||||
irqCounterEnable = data & 0x01;
|
||||
if(irqEnable == 0) cpu.irqLine(0);
|
||||
break;
|
||||
case 14: irqCounter = (irqCounter & 0xff00) | (data << 0); break;
|
||||
case 15: irqCounter = (irqCounter & 0x00ff) | (data << 8); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(addr == 0xc000) {
|
||||
apuPort = data & 0x0f;
|
||||
}
|
||||
|
||||
if(addr == 0xe000) {
|
||||
switch(apuPort) {
|
||||
case 0: pulse[0].frequency = (pulse[0].frequency & 0xff00) | (data << 0); break;
|
||||
case 1: pulse[0].frequency = (pulse[0].frequency & 0x00ff) | (data << 8); break;
|
||||
case 2: pulse[1].frequency = (pulse[1].frequency & 0xff00) | (data << 0); break;
|
||||
case 3: pulse[1].frequency = (pulse[1].frequency & 0x00ff) | (data << 8); break;
|
||||
case 4: pulse[2].frequency = (pulse[2].frequency & 0xff00) | (data << 0); break;
|
||||
case 5: pulse[2].frequency = (pulse[2].frequency & 0x00ff) | (data << 8); break;
|
||||
case 7:
|
||||
pulse[0].disable = data & 0x01;
|
||||
pulse[1].disable = data & 0x02;
|
||||
pulse[2].disable = data & 0x04;
|
||||
break;
|
||||
case 8: pulse[0].volume = data & 0x0f; break;
|
||||
case 9: pulse[1].volume = data & 0x0f; break;
|
||||
case 10: pulse[2].volume = data & 0x0f; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) -> uint {
|
||||
uint8 bank = (addr >> 10) & 7;
|
||||
return (chrBank[bank] << 10) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //first
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //second
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
return Board::readCHR(addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
return Board::writeCHR(addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
for(int n : range(16)) {
|
||||
double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n));
|
||||
dac[n] = volume * 8192.0;
|
||||
}
|
||||
|
||||
mmuPort = 0;
|
||||
apuPort = 0;
|
||||
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
irqEnable = 0;
|
||||
irqCounterEnable = 0;
|
||||
irqCounter = 0;
|
||||
|
||||
pulse[0].power();
|
||||
pulse[1].power();
|
||||
pulse[2].power();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(mmuPort);
|
||||
s.integer(apuPort);
|
||||
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqCounterEnable);
|
||||
s.integer(irqCounter);
|
||||
|
||||
pulse[0].serialize(s);
|
||||
pulse[1].serialize(s);
|
||||
pulse[2].serialize(s);
|
||||
}
|
||||
|
||||
uint4 mmuPort;
|
||||
uint4 apuPort;
|
||||
|
||||
uint8 prgBank[4];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
bool irqEnable;
|
||||
bool irqCounterEnable;
|
||||
uint16 irqCounter;
|
||||
|
||||
int16 dac[16];
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "chip/chip.cpp"
|
||||
#include "board/board.cpp"
|
||||
#include "serialization.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cartridge.main();
|
||||
}
|
||||
|
||||
auto Cartridge::main() -> void {
|
||||
board->main();
|
||||
}
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
if(auto loaded = platform->load(ID::Famicom, "Famicom", "fc", {"NTSC-J", "NTSC-U", "PAL"})) {
|
||||
information.pathID = loaded.pathID();
|
||||
information.region = loaded.option();
|
||||
} else return false;
|
||||
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
Board::load(information.manifest); //this call will set Cartridge::board if successful
|
||||
if(!board) return false;
|
||||
|
||||
Hash::SHA256 sha;
|
||||
sha.input(board->prgrom.data, board->prgrom.size);
|
||||
sha.input(board->chrrom.data, board->chrrom.size);
|
||||
information.sha256 = sha.digest();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
board->save();
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
delete board;
|
||||
board = nullptr;
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
create(Cartridge::Enter, system.frequency());
|
||||
board->power();
|
||||
}
|
||||
|
||||
auto Cartridge::readPRG(uint addr) -> uint8 {
|
||||
return board->readPRG(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::writePRG(uint addr, uint8 data) -> void {
|
||||
return board->writePRG(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::readCHR(uint addr) -> uint8 {
|
||||
return board->readCHR(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::writeCHR(uint addr, uint8 data) -> void {
|
||||
return board->writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::scanline(uint y) -> void {
|
||||
return board->scanline(y);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "chip/chip.hpp"
|
||||
#include "board/board.hpp"
|
||||
|
||||
struct Cartridge : Thread {
|
||||
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
|
||||
|
||||
//cartridge.cpp
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto region() const -> string { return information.region; }
|
||||
auto sha256() const -> string { return information.sha256; }
|
||||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Information {
|
||||
uint pathID = 0;
|
||||
string region;
|
||||
string sha256;
|
||||
string manifest;
|
||||
string title;
|
||||
} information;
|
||||
|
||||
//privileged:
|
||||
Board* board = nullptr;
|
||||
|
||||
auto readPRG(uint addr) -> uint8;
|
||||
auto writePRG(uint addr, uint8 data) -> void;
|
||||
|
||||
auto readCHR(uint addr) -> uint8;
|
||||
auto writeCHR(uint addr, uint8 data) -> void;
|
||||
|
||||
//scanline() is for debugging purposes only:
|
||||
//boards must detect scanline edges on their own
|
||||
auto scanline(uint y) -> void;
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "mmc1.cpp"
|
||||
#include "mmc3.cpp"
|
||||
#include "mmc5.cpp"
|
||||
#include "mmc6.cpp"
|
||||
#include "vrc1.cpp"
|
||||
#include "vrc2.cpp"
|
||||
#include "vrc3.cpp"
|
||||
#include "vrc4.cpp"
|
||||
#include "vrc6.cpp"
|
||||
#include "vrc7.cpp"
|
||||
|
||||
Chip::Chip(Board& board) : board(board) {
|
||||
}
|
||||
|
||||
auto Chip::tick() -> void {
|
||||
board.tick();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
struct Board;
|
||||
|
||||
struct Chip {
|
||||
Chip(Board& board);
|
||||
auto tick() -> void;
|
||||
|
||||
Board& board;
|
||||
};
|
||||
@@ -1,125 +0,0 @@
|
||||
struct MMC1 : Chip {
|
||||
MMC1(Board& board) : Chip(board) {
|
||||
revision = Revision::MMC1B2;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(writedelay) writedelay--;
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) -> uint {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (prgBank & ~1) + region;
|
||||
|
||||
if(prgSize) {
|
||||
bank = (region == 0 ? 0x0 : 0xf);
|
||||
if(region != prgMode) bank = prgBank;
|
||||
}
|
||||
|
||||
return (bank << 14) | (addr & 0x3fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) -> uint {
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region];
|
||||
if(chrMode == 0) bank = (chrBank[0] & ~1) | region;
|
||||
return (bank << 12) | (addr & 0x0fff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return 0x0000 | (addr & 0x03ff);
|
||||
case 1: return 0x0400 | (addr & 0x03ff);
|
||||
case 2: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
case 3: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
if(writedelay) return;
|
||||
writedelay = 2;
|
||||
|
||||
if(data & 0x80) {
|
||||
shiftaddr = 0;
|
||||
prgSize = 1;
|
||||
prgMode = 1;
|
||||
} else {
|
||||
shiftdata = ((data & 1) << 4) | (shiftdata >> 1);
|
||||
if(++shiftaddr == 5) {
|
||||
shiftaddr = 0;
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
chrMode = (shiftdata & 0x10);
|
||||
prgSize = (shiftdata & 0x08);
|
||||
prgMode = (shiftdata & 0x04);
|
||||
mirror = (shiftdata & 0x03);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
chrBank[0] = (shiftdata & 0x1f);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
chrBank[1] = (shiftdata & 0x1f);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
ramDisable = (shiftdata & 0x10);
|
||||
prgBank = (shiftdata & 0x0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
writedelay = 0;
|
||||
shiftaddr = 0;
|
||||
shiftdata = 0;
|
||||
|
||||
chrMode = 0;
|
||||
prgSize = 1;
|
||||
prgMode = 1;
|
||||
mirror = 0;
|
||||
chrBank[0] = 0;
|
||||
chrBank[1] = 1;
|
||||
ramDisable = 0;
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(writedelay);
|
||||
s.integer(shiftaddr);
|
||||
s.integer(shiftdata);
|
||||
|
||||
s.integer(chrMode);
|
||||
s.integer(prgSize);
|
||||
s.integer(prgMode);
|
||||
s.integer(mirror);
|
||||
s.array(chrBank);
|
||||
s.integer(ramDisable);
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
MMC1,
|
||||
MMC1A,
|
||||
MMC1B1,
|
||||
MMC1B2,
|
||||
MMC1B3,
|
||||
MMC1C,
|
||||
} revision;
|
||||
|
||||
uint writedelay;
|
||||
uint shiftaddr;
|
||||
uint shiftdata;
|
||||
|
||||
bool chrMode;
|
||||
bool prgSize; //0 = 32K, 1 = 16K
|
||||
bool prgMode;
|
||||
uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal
|
||||
uint5 chrBank[2];
|
||||
bool ramDisable;
|
||||
uint4 prgBank;
|
||||
};
|
||||
@@ -1,178 +0,0 @@
|
||||
struct MMC3 : Chip {
|
||||
MMC3(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqDelay) irqDelay--;
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irqTest(uint addr) -> void {
|
||||
if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
|
||||
if(irqDelay == 0) {
|
||||
if(irqCounter == 0) {
|
||||
irqCounter = irqLatch;
|
||||
} else if(--irqCounter == 0) {
|
||||
if(irqEnable) irqLine = 1;
|
||||
}
|
||||
}
|
||||
irqDelay = 6;
|
||||
}
|
||||
chrAbus = addr;
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 1:
|
||||
return (prgBank[1] << 13) | (addr & 0x1fff);
|
||||
case 2:
|
||||
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
if(chrMode == 0) {
|
||||
if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
|
||||
} else {
|
||||
if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
if(ramEnable) return board.prgram.data[addr & 0x1fff];
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(ramEnable && !ramWriteProtect) board.prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xe001) {
|
||||
case 0x8000:
|
||||
chrMode = data & 0x80;
|
||||
prgMode = data & 0x40;
|
||||
bankSelect = data & 0x07;
|
||||
break;
|
||||
|
||||
case 0x8001:
|
||||
switch(bankSelect) {
|
||||
case 0: chrBank[0] = data & ~1; break;
|
||||
case 1: chrBank[1] = data & ~1; break;
|
||||
case 2: chrBank[2] = data; break;
|
||||
case 3: chrBank[3] = data; break;
|
||||
case 4: chrBank[4] = data; break;
|
||||
case 5: chrBank[5] = data; break;
|
||||
case 6: prgBank[0] = data & 0x3f; break;
|
||||
case 7: prgBank[1] = data & 0x3f; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
ramEnable = data & 0x80;
|
||||
ramWriteProtect = data & 0x40;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irqCounter = 0;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
irqEnable = false;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xe001:
|
||||
irqEnable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
chrMode = 0;
|
||||
prgMode = 0;
|
||||
bankSelect = 0;
|
||||
prgBank[0] = 0;
|
||||
prgBank[1] = 0;
|
||||
chrBank[0] = 0;
|
||||
chrBank[1] = 0;
|
||||
chrBank[2] = 0;
|
||||
chrBank[3] = 0;
|
||||
chrBank[4] = 0;
|
||||
chrBank[5] = 0;
|
||||
mirror = 0;
|
||||
ramEnable = 1;
|
||||
ramWriteProtect = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter = 0;
|
||||
irqEnable = false;
|
||||
irqDelay = 0;
|
||||
irqLine = 0;
|
||||
|
||||
chrAbus = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(chrMode);
|
||||
s.integer(prgMode);
|
||||
s.integer(bankSelect);
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(ramEnable);
|
||||
s.integer(ramWriteProtect);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqDelay);
|
||||
s.integer(irqLine);
|
||||
|
||||
s.integer(chrAbus);
|
||||
}
|
||||
|
||||
bool chrMode;
|
||||
bool prgMode;
|
||||
uint3 bankSelect;
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[6];
|
||||
bool mirror;
|
||||
bool ramEnable;
|
||||
bool ramWriteProtect;
|
||||
uint8 irqLatch;
|
||||
uint8 irqCounter;
|
||||
bool irqEnable;
|
||||
uint irqDelay;
|
||||
bool irqLine;
|
||||
|
||||
uint16 chrAbus;
|
||||
};
|
||||
@@ -1,486 +0,0 @@
|
||||
struct MMC5 : Chip {
|
||||
MMC5(Board& board) : Chip(board) {
|
||||
revision = Revision::MMC5;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
//scanline() resets this; if no scanlines detected, enter video blanking period
|
||||
if(++cpuCycleCounter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
||||
|
||||
cpu.irqLine(irqEnable && irqPending);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
//used for testing only, to verify MMC5 scanline detection is accurate:
|
||||
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
|
||||
}
|
||||
|
||||
auto accessPRG(bool write, uint addr, uint8 data = 0x00) -> uint8 {
|
||||
uint bank;
|
||||
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
bank = (ramSelect << 2) | ramBank;
|
||||
addr &= 0x1fff;
|
||||
} else if(prgMode == 0) {
|
||||
bank = prgBank[3] & ~3;
|
||||
addr &= 0x7fff;
|
||||
} else if(prgMode == 1) {
|
||||
if((addr & 0xc000) == 0x8000) bank = (prgBank[1] & ~1);
|
||||
if((addr & 0xe000) == 0xc000) bank = (prgBank[3] & ~1);
|
||||
addr &= 0x3fff;
|
||||
} else if(prgMode == 2) {
|
||||
if((addr & 0xe000) == 0x8000) bank = (prgBank[1] & ~1) | 0;
|
||||
if((addr & 0xe000) == 0xa000) bank = (prgBank[1] & ~1) | 1;
|
||||
if((addr & 0xe000) == 0xc000) bank = (prgBank[2]);
|
||||
if((addr & 0xe000) == 0xe000) bank = (prgBank[3]);
|
||||
addr &= 0x1fff;
|
||||
} else if(prgMode == 3) {
|
||||
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
|
||||
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
|
||||
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
|
||||
if((addr & 0xe000) == 0xe000) bank = prgBank[3];
|
||||
addr &= 0x1fff;
|
||||
}
|
||||
|
||||
bool rom = bank & 0x80;
|
||||
bank &= 0x7f;
|
||||
|
||||
if(write == false) {
|
||||
if(rom) {
|
||||
return board.prgrom.read((bank << 13) | addr);
|
||||
} else {
|
||||
return board.prgram.read((bank << 13) | addr);
|
||||
}
|
||||
} else {
|
||||
if(rom) {
|
||||
board.prgrom.write((bank << 13) | addr, data);
|
||||
} else {
|
||||
if(prgramWriteProtect[0] == 2 && prgramWriteProtect[1] == 1) {
|
||||
board.prgram.write((bank << 13) | addr, data);
|
||||
}
|
||||
}
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
if(exramMode >= 2) return exram[addr & 0x03ff];
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
if(addr >= 0x6000) {
|
||||
return accessPRG(0, addr);
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x5204: {
|
||||
uint8 result = (irqPending << 7) | (inFrame << 6);
|
||||
irqPending = false;
|
||||
return result;
|
||||
}
|
||||
case 0x5205: return (multiplier * multiplicand) >> 0;
|
||||
case 0x5206: return (multiplier * multiplicand) >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
//writes 0x00 *during* Vblank (not during screen rendering ...)
|
||||
if(exramMode == 0 || exramMode == 1) exram[addr & 0x03ff] = inFrame ? data : (uint8)0x00;
|
||||
if(exramMode == 2) exram[addr & 0x03ff] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr >= 0x6000) {
|
||||
accessPRG(1, addr, data);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x2000:
|
||||
sprite8x16 = data & 0x20;
|
||||
break;
|
||||
|
||||
case 0x2001:
|
||||
//if BG+sprites are disabled; enter video blanking period
|
||||
if((data & 0x18) == 0) blank();
|
||||
break;
|
||||
|
||||
case 0x5100: prgMode = data & 3; break;
|
||||
case 0x5101: chrMode = data & 3; break;
|
||||
|
||||
case 0x5102: prgramWriteProtect[0] = data & 3; break;
|
||||
case 0x5103: prgramWriteProtect[1] = data & 3; break;
|
||||
|
||||
case 0x5104:
|
||||
exramMode = data & 3;
|
||||
break;
|
||||
|
||||
case 0x5105:
|
||||
nametableMode[0] = (data & 0x03) >> 0;
|
||||
nametableMode[1] = (data & 0x0c) >> 2;
|
||||
nametableMode[2] = (data & 0x30) >> 4;
|
||||
nametableMode[3] = (data & 0xc0) >> 6;
|
||||
break;
|
||||
|
||||
case 0x5106:
|
||||
fillmodeTile = data;
|
||||
break;
|
||||
|
||||
case 0x5107:
|
||||
fillmodeColor = data & 3;
|
||||
fillmodeColor |= fillmodeColor << 2;
|
||||
fillmodeColor |= fillmodeColor << 4;
|
||||
break;
|
||||
|
||||
case 0x5113:
|
||||
ramSelect = data & 0x04;
|
||||
ramBank = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0x5114: prgBank[0] = data; break;
|
||||
case 0x5115: prgBank[1] = data; break;
|
||||
case 0x5116: prgBank[2] = data; break;
|
||||
case 0x5117: prgBank[3] = data | 0x80; break;
|
||||
|
||||
case 0x5120: chrSpriteBank[0] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5121: chrSpriteBank[1] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5122: chrSpriteBank[2] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5123: chrSpriteBank[3] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5124: chrSpriteBank[4] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5125: chrSpriteBank[5] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5126: chrSpriteBank[6] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5127: chrSpriteBank[7] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
|
||||
case 0x5128: chrBGBank[0] = (chrBankHi << 8) | data; chrActive = 1; break;
|
||||
case 0x5129: chrBGBank[1] = (chrBankHi << 8) | data; chrActive = 1; break;
|
||||
case 0x512a: chrBGBank[2] = (chrBankHi << 8) | data; chrActive = 1; break;
|
||||
case 0x512b: chrBGBank[3] = (chrBankHi << 8) | data; chrActive = 1; break;
|
||||
|
||||
case 0x5130:
|
||||
chrBankHi = data & 3;
|
||||
break;
|
||||
|
||||
case 0x5200:
|
||||
vsEnable = data & 0x80;
|
||||
vsSide = data & 0x40;
|
||||
vsTile = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x5201:
|
||||
vsScroll = data;
|
||||
break;
|
||||
|
||||
case 0x5202:
|
||||
vsBank = data;
|
||||
break;
|
||||
|
||||
case 0x5203:
|
||||
irqLine = data;
|
||||
break;
|
||||
|
||||
case 0x5204:
|
||||
irqEnable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0x5205:
|
||||
multiplicand = data;
|
||||
break;
|
||||
|
||||
case 0x5206:
|
||||
multiplier = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto chrSpriteAddr(uint addr) -> uint {
|
||||
if(chrMode == 0) {
|
||||
auto bank = chrSpriteBank[7];
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
if(chrMode == 1) {
|
||||
auto bank = chrSpriteBank[(addr / 0x1000) * 4 + 3];
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chrMode == 2) {
|
||||
auto bank = chrSpriteBank[(addr / 0x0800) * 2 + 1];
|
||||
return (bank * 0x0800) + (addr & 0x07ff);
|
||||
}
|
||||
|
||||
if(chrMode == 3) {
|
||||
auto bank = chrSpriteBank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chrBGAddr(uint addr) -> uint {
|
||||
addr &= 0x0fff;
|
||||
|
||||
if(chrMode == 0) {
|
||||
auto bank = chrBGBank[3];
|
||||
return (bank * 0x2000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chrMode == 1) {
|
||||
auto bank = chrBGBank[3];
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chrMode == 2) {
|
||||
auto bank = chrBGBank[(addr / 0x0800) * 2 + 1];
|
||||
return (bank * 0x0800) + (addr & 0x07ff);
|
||||
}
|
||||
|
||||
if(chrMode == 3) {
|
||||
auto bank = chrBGBank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chrVSAddr(uint addr) -> uint {
|
||||
return (vsBank * 0x1000) + (addr & 0x0ff8) + (vsVpos & 7);
|
||||
}
|
||||
|
||||
auto blank() -> void {
|
||||
inFrame = false;
|
||||
}
|
||||
|
||||
auto scanline() -> void {
|
||||
hcounter = 0;
|
||||
|
||||
if(inFrame == false) {
|
||||
inFrame = true;
|
||||
irqPending = false;
|
||||
vcounter = 0;
|
||||
} else {
|
||||
if(vcounter == irqLine) irqPending = true;
|
||||
vcounter++;
|
||||
}
|
||||
|
||||
cpuCycleCounter = 0;
|
||||
}
|
||||
|
||||
auto readCIRAM(uint addr) -> uint8 {
|
||||
if(vsFetch && (hcounter & 2) == 0) return exram[vsVpos / 8 * 32 + vsHpos / 8];
|
||||
if(vsFetch && (hcounter & 2) != 0) return exram[vsVpos / 32 * 8 + vsHpos / 32 + 0x03c0];
|
||||
|
||||
switch(nametableMode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.readCIRAM(0x0000 | (addr & 0x03ff));
|
||||
case 1: return ppu.readCIRAM(0x0400 | (addr & 0x03ff));
|
||||
case 2: return exramMode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
|
||||
case 3: return (hcounter & 2) == 0 ? fillmodeTile : fillmodeColor;
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
chrAccess[0] = chrAccess[1];
|
||||
chrAccess[1] = chrAccess[2];
|
||||
chrAccess[2] = chrAccess[3];
|
||||
chrAccess[3] = addr;
|
||||
|
||||
//detect two unused nametable fetches at end of each scanline
|
||||
if((chrAccess[0] & 0x2000) == 0
|
||||
&& (chrAccess[1] & 0x2000)
|
||||
&& (chrAccess[2] & 0x2000)
|
||||
&& (chrAccess[3] & 0x2000)) scanline();
|
||||
|
||||
if(inFrame == false) {
|
||||
vsFetch = false;
|
||||
if(addr & 0x2000) return readCIRAM(addr);
|
||||
return board.chrrom.read(chrActive ? chrBGAddr(addr) : chrSpriteAddr(addr));
|
||||
}
|
||||
|
||||
bool bgFetch = (hcounter < 256 || hcounter >= 320);
|
||||
uint8 result = 0x00;
|
||||
|
||||
if((hcounter & 7) == 0) {
|
||||
vsHpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
|
||||
vsVpos = vcounter + vsScroll;
|
||||
vsFetch = vsEnable && bgFetch && exramMode < 2
|
||||
&& (vsSide ? vsHpos / 8 >= vsTile : vsHpos / 8 < vsTile);
|
||||
if(vsVpos >= 240) vsVpos -= 240;
|
||||
|
||||
result = readCIRAM(addr);
|
||||
|
||||
exbank = (chrBankHi << 6) | (exram[addr & 0x03ff] & 0x3f);
|
||||
exattr = exram[addr & 0x03ff] >> 6;
|
||||
exattr |= exattr << 2;
|
||||
exattr |= exattr << 4;
|
||||
} else if((hcounter & 7) == 2) {
|
||||
result = readCIRAM(addr);
|
||||
if(bgFetch && exramMode == 1) result = exattr;
|
||||
} else {
|
||||
if(vsFetch) result = board.chrrom.read(chrVSAddr(addr));
|
||||
else if(sprite8x16 ? bgFetch : chrActive) result = board.chrrom.read(chrBGAddr(addr));
|
||||
else result = board.chrrom.read(chrSpriteAddr(addr));
|
||||
if(bgFetch && exramMode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
|
||||
}
|
||||
|
||||
hcounter += 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
switch(nametableMode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.writeCIRAM(0x0000 | (addr & 0x03ff), data);
|
||||
case 1: return ppu.writeCIRAM(0x0400 | (addr & 0x03ff), data);
|
||||
case 2: exram[addr & 0x03ff] = data; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
for(auto& n : exram) n = 0xff;
|
||||
|
||||
prgMode = 3;
|
||||
chrMode = 0;
|
||||
for(auto& n : prgramWriteProtect) n = 0;
|
||||
exramMode = 0;
|
||||
for(auto& n : nametableMode) n = 0;
|
||||
fillmodeTile = 0;
|
||||
fillmodeColor = 0;
|
||||
ramSelect = 0;
|
||||
ramBank = 0;
|
||||
prgBank[0] = 0x00;
|
||||
prgBank[1] = 0x00;
|
||||
prgBank[2] = 0x00;
|
||||
prgBank[3] = 0xff;
|
||||
for(auto& n : chrSpriteBank) n = 0;
|
||||
for(auto& n : chrBGBank) n = 0;
|
||||
chrBankHi = 0;
|
||||
vsEnable = 0;
|
||||
vsSide = 0;
|
||||
vsTile = 0;
|
||||
vsScroll = 0;
|
||||
vsBank = 0;
|
||||
irqLine = 0;
|
||||
irqEnable = 0;
|
||||
multiplicand = 0;
|
||||
multiplier = 0;
|
||||
|
||||
cpuCycleCounter = 0;
|
||||
irqCounter = 0;
|
||||
irqPending = 0;
|
||||
inFrame = 0;
|
||||
vcounter = 0;
|
||||
hcounter = 0;
|
||||
for(auto& n : chrAccess) n = 0;
|
||||
chrActive = 0;
|
||||
sprite8x16 = 0;
|
||||
|
||||
exbank = 0;
|
||||
exattr = 0;
|
||||
|
||||
vsFetch = 0;
|
||||
vsVpos = 0;
|
||||
vsHpos = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(exram);
|
||||
|
||||
s.integer(prgMode);
|
||||
s.integer(chrMode);
|
||||
for(auto& n : prgramWriteProtect) s.integer(n);
|
||||
s.integer(exramMode);
|
||||
for(auto& n : nametableMode) s.integer(n);
|
||||
s.integer(fillmodeTile);
|
||||
s.integer(fillmodeColor);
|
||||
s.integer(ramSelect);
|
||||
s.integer(ramBank);
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrSpriteBank) s.integer(n);
|
||||
for(auto& n : chrBGBank) s.integer(n);
|
||||
s.integer(chrBankHi);
|
||||
s.integer(vsEnable);
|
||||
s.integer(vsSide);
|
||||
s.integer(vsTile);
|
||||
s.integer(vsScroll);
|
||||
s.integer(vsBank);
|
||||
s.integer(irqLine);
|
||||
s.integer(irqEnable);
|
||||
s.integer(multiplicand);
|
||||
s.integer(multiplier);
|
||||
|
||||
s.integer(cpuCycleCounter);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqPending);
|
||||
s.integer(inFrame);
|
||||
|
||||
s.integer(vcounter);
|
||||
s.integer(hcounter);
|
||||
for(auto& n : chrAccess) s.integer(n);
|
||||
s.integer(chrActive);
|
||||
s.integer(sprite8x16);
|
||||
|
||||
s.integer(exbank);
|
||||
s.integer(exattr);
|
||||
|
||||
s.integer(vsFetch);
|
||||
s.integer(vsVpos);
|
||||
s.integer(vsHpos);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
MMC5,
|
||||
MMC5B,
|
||||
} revision;
|
||||
|
||||
uint8 exram[1024];
|
||||
|
||||
//programmable registers
|
||||
|
||||
uint2 prgMode; //$5100
|
||||
uint2 chrMode; //$5101
|
||||
|
||||
uint2 prgramWriteProtect[2]; //$5102,$5103
|
||||
|
||||
uint2 exramMode; //$5104
|
||||
uint2 nametableMode[4]; //$5105
|
||||
uint8 fillmodeTile; //$5106
|
||||
uint8 fillmodeColor; //$5107
|
||||
|
||||
bool ramSelect; //$5113
|
||||
uint2 ramBank; //$5113
|
||||
uint8 prgBank[4]; //$5114-5117
|
||||
uint10 chrSpriteBank[8]; //$5120-5127
|
||||
uint10 chrBGBank[4]; //$5128-512b
|
||||
uint2 chrBankHi; //$5130
|
||||
|
||||
bool vsEnable; //$5200
|
||||
bool vsSide; //$5200
|
||||
uint5 vsTile; //$5200
|
||||
uint8 vsScroll; //$5201
|
||||
uint8 vsBank; //$5202
|
||||
|
||||
uint8 irqLine; //$5203
|
||||
bool irqEnable; //$5204
|
||||
|
||||
uint8 multiplicand; //$5205
|
||||
uint8 multiplier; //$5206
|
||||
|
||||
//status registers
|
||||
|
||||
uint cpuCycleCounter;
|
||||
uint irqCounter;
|
||||
bool irqPending;
|
||||
bool inFrame;
|
||||
|
||||
uint vcounter;
|
||||
uint hcounter;
|
||||
uint16 chrAccess[4];
|
||||
bool chrActive;
|
||||
bool sprite8x16;
|
||||
|
||||
uint8 exbank;
|
||||
uint8 exattr;
|
||||
|
||||
bool vsFetch;
|
||||
uint8 vsVpos;
|
||||
uint8 vsHpos;
|
||||
};
|
||||
@@ -1,189 +0,0 @@
|
||||
struct MMC6 : Chip {
|
||||
MMC6(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqDelay) irqDelay--;
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irqTest(uint addr) -> void {
|
||||
if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
|
||||
if(irqDelay == 0) {
|
||||
if(irqCounter == 0) {
|
||||
irqCounter = irqLatch;
|
||||
} else if(--irqCounter == 0) {
|
||||
if(irqEnable) irqLine = 1;
|
||||
}
|
||||
}
|
||||
irqDelay = 6;
|
||||
}
|
||||
chrAbus = addr;
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 1:
|
||||
return (prgBank[1] << 13) | (addr & 0x1fff);
|
||||
case 2:
|
||||
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
if(chrMode == 0) {
|
||||
if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
|
||||
} else {
|
||||
if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
if(ramEnable == false) return cpu.mdr();
|
||||
if(ramReadable[0] == false && ramReadable[1] == false) return cpu.mdr();
|
||||
bool region = addr & 0x0200;
|
||||
if(ramReadable[region] == false) return 0x00;
|
||||
return board.prgram.read((region * 0x0200) + (addr & 0x01ff));
|
||||
}
|
||||
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(ramEnable == false) return;
|
||||
bool region = addr & 0x0200;
|
||||
if(ramWritable[region] == false) return;
|
||||
return board.prgram.write((region * 0x0200) + (addr & 0x01ff), data);
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xe001) {
|
||||
case 0x8000:
|
||||
chrMode = data & 0x80;
|
||||
prgMode = data & 0x40;
|
||||
ramEnable = data & 0x20;
|
||||
bankSelect = data & 0x07;
|
||||
if(ramEnable == false) {
|
||||
for(auto &n : ramReadable) n = false;
|
||||
for(auto &n : ramWritable) n = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x8001:
|
||||
switch(bankSelect) {
|
||||
case 0: chrBank[0] = data & ~1; break;
|
||||
case 1: chrBank[1] = data & ~1; break;
|
||||
case 2: chrBank[2] = data; break;
|
||||
case 3: chrBank[3] = data; break;
|
||||
case 4: chrBank[4] = data; break;
|
||||
case 5: chrBank[5] = data; break;
|
||||
case 6: prgBank[0] = data & 0x3f; break;
|
||||
case 7: prgBank[1] = data & 0x3f; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
if(ramEnable == false) break;
|
||||
ramReadable[1] = data & 0x80;
|
||||
ramWritable[1] = data & 0x40;
|
||||
ramReadable[0] = data & 0x20;
|
||||
ramWritable[0] = data & 0x10;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irqCounter = 0;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
irqEnable = false;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xe001:
|
||||
irqEnable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
chrMode = 0;
|
||||
prgMode = 0;
|
||||
ramEnable = 0;
|
||||
bankSelect = 0;
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
for(auto& n : ramReadable) n = 0;
|
||||
for(auto& n : ramWritable) n = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter = 0;
|
||||
irqEnable = 0;
|
||||
irqDelay = 0;
|
||||
irqLine = 0;
|
||||
|
||||
chrAbus = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(chrMode);
|
||||
s.integer(prgMode);
|
||||
s.integer(ramEnable);
|
||||
s.integer(bankSelect);
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrBank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
for(auto& n : ramReadable) s.integer(n);
|
||||
for(auto& n : ramWritable) s.integer(n);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqDelay);
|
||||
s.integer(irqLine);
|
||||
|
||||
s.integer(chrAbus);
|
||||
}
|
||||
|
||||
bool chrMode;
|
||||
bool prgMode;
|
||||
bool ramEnable;
|
||||
uint3 bankSelect;
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[6];
|
||||
bool mirror;
|
||||
bool ramReadable[2];
|
||||
bool ramWritable[2];
|
||||
uint8 irqLatch;
|
||||
uint8 irqCounter;
|
||||
bool irqEnable;
|
||||
uint irqDelay;
|
||||
bool irqLine;
|
||||
|
||||
uint16 chrAbus;
|
||||
};
|
||||
@@ -1,75 +0,0 @@
|
||||
struct VRC1 : Chip {
|
||||
VRC1(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0x0f;
|
||||
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
|
||||
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
|
||||
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBankLo[(bool)(addr & 0x1000)];
|
||||
bank |= chrBankHi[(bool)(addr & 0x1000)] << 4;
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xf000) {
|
||||
case 0x8000:
|
||||
prgBank[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
chrBankHi[1] = data & 0x04;
|
||||
chrBankHi[0] = data & 0x02;
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
prgBank[1] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
prgBank[2] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
chrBankLo[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
chrBankLo[1] = data & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBankLo) n = 0;
|
||||
for(auto& n : chrBankHi) n = 0;
|
||||
mirror = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrBankLo) s.integer(n);
|
||||
for(auto& n : chrBankHi) s.integer(n);
|
||||
s.integer(mirror);
|
||||
}
|
||||
|
||||
uint4 prgBank[3];
|
||||
uint4 chrBankLo[2];
|
||||
bool chrBankHi[2];
|
||||
bool mirror;
|
||||
};
|
||||
@@ -1,105 +0,0 @@
|
||||
struct VRC2 : Chip {
|
||||
VRC2(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prgBank[0]; break;
|
||||
case 0xa000: bank = prgBank[1]; break;
|
||||
case 0xc000: bank = 0x1e; break;
|
||||
case 0xe000: bank = 0x1f; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
if(board.prgram.size == 0) {
|
||||
if((addr & 0xf000) == 0x6000) return cpu.mdr() | latch;
|
||||
return cpu.mdr();
|
||||
}
|
||||
return board.prgram.read(addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(board.prgram.size == 0) {
|
||||
if((addr & 0xf000) == 0x6000) latch = data & 0x01;
|
||||
return;
|
||||
}
|
||||
return board.prgram.write(addr & 0x1fff, data);
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prgBank[0] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x9000: case 0x9001: case 0x9002: case 0x9003:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
|
||||
prgBank[1] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0xb000: chrBank[0] = (chrBank[0] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb001: chrBank[0] = (chrBank[0] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xb002: chrBank[1] = (chrBank[1] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb003: chrBank[1] = (chrBank[1] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc000: chrBank[2] = (chrBank[2] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc001: chrBank[2] = (chrBank[2] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc002: chrBank[3] = (chrBank[3] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc003: chrBank[3] = (chrBank[3] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd000: chrBank[4] = (chrBank[4] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd001: chrBank[4] = (chrBank[4] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd002: chrBank[5] = (chrBank[5] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd003: chrBank[5] = (chrBank[5] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe000: chrBank[6] = (chrBank[6] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe001: chrBank[6] = (chrBank[6] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe002: chrBank[7] = (chrBank[7] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe003: chrBank[7] = (chrBank[7] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
latch = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrBank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
s.integer(latch);
|
||||
}
|
||||
|
||||
uint5 prgBank[2];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
bool latch;
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
struct VRC3 : Chip {
|
||||
VRC3(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) { //16-bit
|
||||
if(++irqCounter.w == 0) {
|
||||
irqLine = 1;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqCounter.w = irqLatch;
|
||||
}
|
||||
}
|
||||
if(irqMode == 1) { //8-bit
|
||||
if(++irqCounter.l == 0) {
|
||||
irqLine = 1;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqCounter.l = irqLatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = (addr < 0xc000 ? (uint)prgBank : 0x0f);
|
||||
return (bank * 0x4000) + (addr & 0x3fff);
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xf000) {
|
||||
case 0x8000: irqLatch = (irqLatch & 0xfff0) | ((data & 0x0f) << 0); break;
|
||||
case 0x9000: irqLatch = (irqLatch & 0xff0f) | ((data & 0x0f) << 4); break;
|
||||
case 0xa000: irqLatch = (irqLatch & 0xf0ff) | ((data & 0x0f) << 8); break;
|
||||
case 0xb000: irqLatch = (irqLatch & 0x0fff) | ((data & 0x0f) << 12); break;
|
||||
|
||||
case 0xc000:
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) irqCounter.w = irqLatch;
|
||||
break;
|
||||
|
||||
case 0xd000:
|
||||
irqLine = 0;
|
||||
irqEnable = irqAcknowledge;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
prgBank = data & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgBank = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter.w = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(prgBank);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqCounter.w);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint4 prgBank;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
uint16 irqLatch;
|
||||
struct {
|
||||
union {
|
||||
uint16_t w;
|
||||
struct { uint8_t order_lsb2(l, h); };
|
||||
};
|
||||
} irqCounter;
|
||||
bool irqLine;
|
||||
};
|
||||
@@ -1,173 +0,0 @@
|
||||
struct VRC4 : Chip {
|
||||
VRC4(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0, banks = board.prgrom.size / 0x2000;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prgMode == 0 ? (uint)prgBank[0] : banks - 2; break;
|
||||
case 0xa000: bank = prgBank[1]; break;
|
||||
case 0xc000: bank = prgMode == 0 ? banks - 2 : (uint)prgBank[0]; break;
|
||||
case 0xe000: bank = banks - 1; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prgBank[0] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x9000: case 0x9001:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0x9002: case 0x9003:
|
||||
prgMode = data & 0x02;
|
||||
break;
|
||||
|
||||
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
|
||||
prgBank[1] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0xb000: chrBank[0] = (chrBank[0] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb001: chrBank[0] = (chrBank[0] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xb002: chrBank[1] = (chrBank[1] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb003: chrBank[1] = (chrBank[1] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc000: chrBank[2] = (chrBank[2] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc001: chrBank[2] = (chrBank[2] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc002: chrBank[3] = (chrBank[3] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc003: chrBank[3] = (chrBank[3] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd000: chrBank[4] = (chrBank[4] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd001: chrBank[4] = (chrBank[4] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd002: chrBank[5] = (chrBank[5] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd003: chrBank[5] = (chrBank[5] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe000: chrBank[6] = (chrBank[6] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe001: chrBank[6] = (chrBank[6] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe002: chrBank[7] = (chrBank[7] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe003: chrBank[7] = (chrBank[7] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xf000:
|
||||
irqLatch = (irqLatch & 0xf0) | ((data & 0x0f) << 0);
|
||||
break;
|
||||
|
||||
case 0xf001:
|
||||
irqLatch = (irqLatch & 0x0f) | ((data & 0x0f) << 4);
|
||||
break;
|
||||
|
||||
case 0xf002:
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf003:
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgMode = 0;
|
||||
for(auto& n : prgBank) n = 0;
|
||||
mirror = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(prgMode);
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
for(auto& n : chrBank) s.integer(n);
|
||||
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
bool prgMode;
|
||||
uint5 prgBank[2];
|
||||
uint2 mirror;
|
||||
uint8 chrBank[8];
|
||||
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irqCounter;
|
||||
int irqScalar;
|
||||
bool irqLine;
|
||||
};
|
||||
@@ -1,310 +0,0 @@
|
||||
struct VRC6 : Chip {
|
||||
VRC6(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
struct Pulse {
|
||||
auto clock() -> void {
|
||||
if(--divider == 0) {
|
||||
divider = frequency + 1;
|
||||
cycle++;
|
||||
output = (mode == 1 || cycle > duty) ? volume : (uint4)0;
|
||||
}
|
||||
|
||||
if(enable == false) output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(mode);
|
||||
s.integer(duty);
|
||||
s.integer(volume);
|
||||
s.integer(enable);
|
||||
s.integer(frequency);
|
||||
|
||||
s.integer(divider);
|
||||
s.integer(cycle);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
bool mode;
|
||||
uint3 duty;
|
||||
uint4 volume;
|
||||
bool enable;
|
||||
uint12 frequency;
|
||||
|
||||
uint12 divider;
|
||||
uint4 cycle;
|
||||
uint4 output;
|
||||
} pulse1, pulse2;
|
||||
|
||||
struct Sawtooth {
|
||||
auto clock() -> void {
|
||||
if(--divider == 0) {
|
||||
divider = frequency + 1;
|
||||
if(++phase == 0) {
|
||||
accumulator += rate;
|
||||
if(++stage == 7) {
|
||||
stage = 0;
|
||||
accumulator = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output = accumulator >> 3;
|
||||
if(enable == false) output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(rate);
|
||||
s.integer(enable);
|
||||
s.integer(frequency);
|
||||
|
||||
s.integer(divider);
|
||||
s.integer(phase);
|
||||
s.integer(stage);
|
||||
s.integer(accumulator);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
uint6 rate;
|
||||
bool enable;
|
||||
uint12 frequency;
|
||||
|
||||
uint12 divider;
|
||||
uint1 phase;
|
||||
uint3 stage;
|
||||
uint8 accumulator;
|
||||
uint5 output;
|
||||
} sawtooth;
|
||||
|
||||
auto main() -> void {
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.irqLine(irqLine);
|
||||
|
||||
pulse1.clock();
|
||||
pulse2.clock();
|
||||
sawtooth.clock();
|
||||
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
|
||||
apu.setSample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
if((addr & 0xc000) == 0x8000) return (prgBank[0] << 14) | (addr & 0x3fff);
|
||||
if((addr & 0xe000) == 0xc000) return (prgBank[1] << 13) | (addr & 0x1fff);
|
||||
if((addr & 0xe000) == 0xe000) return ( 0xff << 13) | (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[(addr >> 10) & 7];
|
||||
return (bank << 10) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
return board.prgram.data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
board.prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prgBank[0] = data;
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
pulse1.mode = data & 0x80;
|
||||
pulse1.duty = (data & 0x70) >> 4;
|
||||
pulse1.volume = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x9001:
|
||||
pulse1.frequency = (pulse1.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0x9002:
|
||||
pulse1.frequency = (pulse1.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
pulse1.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
pulse2.mode = data & 0x80;
|
||||
pulse2.duty = (data & 0x70) >> 4;
|
||||
pulse2.volume = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
pulse2.frequency = (pulse2.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0xa002:
|
||||
pulse2.frequency = (pulse2.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
pulse2.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xb000:
|
||||
sawtooth.rate = data & 0x3f;
|
||||
break;
|
||||
|
||||
case 0xb001:
|
||||
sawtooth.frequency = (sawtooth.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0xb002:
|
||||
sawtooth.frequency = (sawtooth.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
sawtooth.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xb003:
|
||||
mirror = (data >> 2) & 3;
|
||||
break;
|
||||
|
||||
case 0xc000: case 0xc001: case 0xc002: case 0xc003:
|
||||
prgBank[1] = data;
|
||||
break;
|
||||
|
||||
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
|
||||
chrBank[0 + (addr & 3)] = data;
|
||||
break;
|
||||
|
||||
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
|
||||
chrBank[4 + (addr & 3)] = data;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xf001:
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf002:
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
prgBank[0] = 0;
|
||||
prgBank[1] = 0;
|
||||
chrBank[0] = 0;
|
||||
chrBank[1] = 0;
|
||||
chrBank[2] = 0;
|
||||
chrBank[3] = 0;
|
||||
chrBank[4] = 0;
|
||||
chrBank[5] = 0;
|
||||
chrBank[6] = 0;
|
||||
chrBank[7] = 0;
|
||||
mirror = 0;
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
|
||||
pulse1.mode = 0;
|
||||
pulse1.duty = 0;
|
||||
pulse1.volume = 0;
|
||||
pulse1.enable = 0;
|
||||
pulse1.frequency = 0;
|
||||
|
||||
pulse1.divider = 1;
|
||||
pulse1.cycle = 0;
|
||||
pulse1.output = 0;
|
||||
|
||||
pulse2.mode = 0;
|
||||
pulse2.duty = 0;
|
||||
pulse2.volume = 0;
|
||||
pulse2.enable = 0;
|
||||
pulse2.frequency = 0;
|
||||
|
||||
pulse2.divider = 1;
|
||||
pulse2.cycle = 0;
|
||||
pulse2.output = 0;
|
||||
|
||||
sawtooth.rate = 0;
|
||||
sawtooth.enable = 0;
|
||||
sawtooth.frequency = 0;
|
||||
|
||||
sawtooth.divider = 1;
|
||||
sawtooth.phase = 0;
|
||||
sawtooth.stage = 0;
|
||||
sawtooth.accumulator = 0;
|
||||
sawtooth.output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
pulse1.serialize(s);
|
||||
pulse2.serialize(s);
|
||||
sawtooth.serialize(s);
|
||||
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irqCounter;
|
||||
int irqScalar;
|
||||
bool irqLine;
|
||||
};
|
||||
@@ -1,143 +0,0 @@
|
||||
//Konami VRC7
|
||||
//Yamaha YM2413 OPLL audio - not emulated
|
||||
|
||||
struct VRC7 : Chip {
|
||||
VRC7(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.irqLine(irqLine);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: prgBank[0] = data; break;
|
||||
case 0x8010: prgBank[1] = data; break;
|
||||
case 0x9000: prgBank[2] = data; break;
|
||||
case 0x9010: break; //APU addr port
|
||||
case 0x9030: break; //APU data port
|
||||
case 0xa000: chrBank[0] = data; break;
|
||||
case 0xa010: chrBank[1] = data; break;
|
||||
case 0xb000: chrBank[2] = data; break;
|
||||
case 0xb010: chrBank[3] = data; break;
|
||||
case 0xc000: chrBank[4] = data; break;
|
||||
case 0xc010: chrBank[5] = data; break;
|
||||
case 0xd000: chrBank[6] = data; break;
|
||||
case 0xd010: chrBank[7] = data; break;
|
||||
case 0xe000: mirror = data & 0x03; break;
|
||||
|
||||
case 0xe010:
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf010:
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prgBank[0]; break;
|
||||
case 0xa000: bank = prgBank[1]; break;
|
||||
case 0xc000: bank = prgBank[2]; break;
|
||||
case 0xe000: bank = 0xff; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint8 prgBank[3];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irqCounter;
|
||||
int irqScalar;
|
||||
bool irqLine;
|
||||
};
|
||||
Loaded 100 of 2103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user