Compare commits
161 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7786206a4f | ||
|
fbc1571889 | ||
|
53843934c0 | ||
|
37b610da53 | ||
|
2d9ce59e99 | ||
|
559a6585ef | ||
|
25145f59cc | ||
|
17fc6d8d51 | ||
|
6871e0e32a | ||
|
bb1dd8c609 | ||
|
c9f7c6c4be | ||
|
95d0020297 | ||
|
41148b1024 | ||
|
dbee893408 | ||
|
cb86cd116c | ||
|
1a889ae232 | ||
|
79be6f2355 | ||
|
cac3858f65 | ||
|
1fd6d983da | ||
|
aaf094e7c4 | ||
|
3159285eaa | ||
|
90da691717 | ||
|
598076e400 | ||
|
075f540ec4 | ||
|
41eccf6ec4 | ||
|
4c4e79aa0e | ||
|
0b44399c0a | ||
|
23dd28952b | ||
|
03b06257d3 | ||
|
336d20123f | ||
|
c58169945c | ||
|
c2d0ed4ca8 | ||
|
3d34517f3e | ||
|
a3e0f6da25 | ||
|
bd814f0358 | ||
|
f9adb4d2c6 | ||
|
9a6ae6dacb | ||
|
1e4affe5f9 | ||
|
93a6a1ce7e | ||
|
3b4e8b6d75 | ||
|
b2b51d544f | ||
|
5da4532771 | ||
|
552d385031 | ||
|
0595e9e866 | ||
|
23da4e4e91 | ||
|
41e127a07c | ||
|
5d135b556d | ||
|
2335bb0df8 | ||
|
212da0a966 | ||
|
5deba5cbc1 | ||
|
716c95f279 | ||
|
876b4be1d2 | ||
|
22bd4b9277 | ||
|
f1a4576ac4 | ||
|
0aedb3430c | ||
|
35ff15f83e | ||
|
65a3e6c676 | ||
|
393c2395bb | ||
|
6090c63958 | ||
|
0c55796060 | ||
|
372e9ef42b | ||
|
40a5fbe605 | ||
|
ec960c5172 | ||
|
b14c6bf155 | ||
|
5b97fa2415 | ||
|
f70a20bc42 | ||
|
470e27323d | ||
|
5a8c814e25 | ||
|
91bb781b73 | ||
|
15b67922b3 | ||
|
173a5d67bc | ||
|
ec9729a9e1 | ||
|
77ac5f9e88 | ||
|
8c337d4ac6 | ||
|
73354923eb | ||
|
3aa90590ca | ||
|
52d0cd8dfb | ||
|
c67fb2c726 | ||
|
5d29700fa1 | ||
|
5e7fdbe2c0 | ||
|
51e3fcd3fa | ||
|
18852bcbe2 | ||
|
bcc2627793 | ||
|
685cec6583 | ||
|
6882bd98cf | ||
|
6c8e3c885d | ||
|
8f5bc80f01 | ||
|
2b8df2e70e | ||
|
ec4ab1dc11 | ||
|
5961ea9c03 | ||
|
3353efd3a1 | ||
|
a73a94f331 | ||
|
7ee1534093 | ||
|
87e2154ea1 | ||
|
d8bd1fca1f | ||
|
7acbf5c3dd | ||
|
ea11c6d098 | ||
|
f5b96e9e9e | ||
|
6078cdacbb | ||
|
c2648faeab | ||
|
2e14bd1c81 | ||
|
cd5dde0f62 | ||
|
e5f19e49d4 | ||
|
c6ed8bb4b1 | ||
|
c24eb6e592 | ||
|
d537eaa0fd | ||
|
8bbbc5e737 | ||
|
210306e661 | ||
|
6847058210 | ||
|
b7dca2f317 | ||
|
b69909be8d | ||
|
8617711ea2 | ||
|
540d960e30 | ||
|
8023d9cbe8 | ||
|
0ea17abfea | ||
|
8f61c267c5 | ||
|
eaa2c1f6c0 | ||
|
985610c167 | ||
|
72b824cf1a | ||
|
2dd35f984d | ||
|
e216912ca3 | ||
|
a4a3d611a6 | ||
|
5c55cc2c94 | ||
|
c49d3b2006 | ||
|
61091167b8 | ||
|
610d42d573 | ||
|
f8a6cc2cbd | ||
|
3a175ad2b0 | ||
|
5e330da4e8 | ||
|
c38a771f22 | ||
|
3d8be92550 | ||
|
38fbcd5277 | ||
|
0cb3529547 | ||
|
2f81b5a3e7 | ||
|
aef8d5e962 | ||
|
a5af5eab3c | ||
|
41efdba45a | ||
|
b55783c322 | ||
|
3ec08cebbe | ||
|
8d7d452534 | ||
|
56cb9c01a5 | ||
|
e9d2d56df9 | ||
|
6d487925d0 | ||
|
7aad868adb | ||
|
dd06dd0fed | ||
|
3c26736d4b | ||
|
f53cb33eb9 | ||
|
426de198b7 | ||
|
9e06857e4d | ||
|
e28aa32324 | ||
|
a9571ff5b8 | ||
|
3d21e9afe0 | ||
|
1cb37fc974 | ||
|
f92fc276af | ||
|
18afd41a80 | ||
|
3507c522f4 | ||
|
7a4bfca106 | ||
|
0db886f91d | ||
|
904d11a3f7 | ||
|
214b921388 | ||
|
b8b5aef165 |
5
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
higan/profile/WonderSwan.sys/internal.ram
|
||||
higan/profile/WonderSwan Color.sys/internal.ram
|
||||
higan/systems/WonderSwan.sys/internal.ram
|
||||
higan/systems/WonderSwan Color.sys/internal.ram
|
||||
higan/systems/Super Famicom.sys/configuration.bml
|
||||
docs_build/
|
||||
|
@@ -1,39 +1,71 @@
|
||||
# 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
|
||||
image: ubuntu:latest
|
||||
|
||||
linux-x86_64-binaries:
|
||||
higan-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
|
||||
- make -C icarus compiler=g++
|
||||
- make -C higan compiler=g++
|
||||
- apt-get update && apt-get -y install build-essential libgtk2.0-dev libpulse-dev mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl2-dev libxv-dev libao-dev libopenal-dev libudev-dev mkdocs
|
||||
- make -C genius
|
||||
- make -C icarus
|
||||
- make -C higan target=higan
|
||||
- LC_ALL=C.UTF-8 mkdocs build
|
||||
- mkdir higan-nightly
|
||||
- cp -a genius/out/genius higan-nightly/genius
|
||||
- cp -a icarus/out/icarus higan-nightly/icarus
|
||||
- cp -a icarus/Database higan-nightly/
|
||||
- cp -a icarus/Firmware 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 higan/systems/ higan-nightly/
|
||||
- cp -a shaders higan-nightly/
|
||||
- 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.
|
||||
bsnes-linux-x86_64-binaries:
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential mingw-w64
|
||||
- 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"
|
||||
- apt-get update && apt-get -y install build-essential libgtk2.0-dev libpulse-dev mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl2-dev libxv-dev libao-dev libopenal-dev libudev-dev
|
||||
- make -C higan target=bsnes
|
||||
- mkdir bsnes-nightly
|
||||
- cp -a higan/out/bsnes bsnes-nightly/bsnes
|
||||
- cp -a shaders bsnes-nightly/
|
||||
- cp -a GPLv3.txt bsnes-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- bsnes-nightly/*
|
||||
|
||||
higan-windows-x86_64-binaries:
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential mingw-w64 mkdocs
|
||||
# genius does not currently build on Windows due to lack of a combo edit control in hiro
|
||||
#- make -C genius platform=windows compiler="x86_64-w64-mingw32-g++" windres="x86_64-w64-mingw32-windres"
|
||||
- make -C icarus platform=windows compiler="x86_64-w64-mingw32-g++" windres="x86_64-w64-mingw32-windres"
|
||||
- make -C higan target=higan platform=windows compiler="x86_64-w64-mingw32-g++" windres="x86_64-w64-mingw32-windres"
|
||||
- LC_ALL=C.UTF-8 mkdocs build
|
||||
- mkdir higan-nightly
|
||||
#- cp -a genius/out/genius higan-nightly/genius.exe
|
||||
- cp -a icarus/out/icarus higan-nightly/icarus.exe
|
||||
- cp -a icarus/Database higan-nightly/
|
||||
- cp -a icarus/Firmware 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 higan/systems/ higan-nightly/
|
||||
- cp -a shaders higan-nightly/
|
||||
- cp -a docs_build higan-nightly/docs
|
||||
- cp -a GPLv3.txt higan-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- higan-nightly/*
|
||||
|
||||
bsnes-windows-x86_64-binaries:
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential mingw-w64
|
||||
- make -C higan target=bsnes platform=windows compiler="x86_64-w64-mingw32-g++" windres="x86_64-w64-mingw32-windres"
|
||||
- mkdir bsnes-nightly
|
||||
- cp -a higan/out/bsnes bsnes-nightly/bsnes.exe
|
||||
- cp -a shaders bsnes-nightly/
|
||||
- cp -a GPLv3.txt bsnes-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- bsnes-nightly/*
|
||||
|
@@ -2,8 +2,8 @@ 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,
|
||||
you should create an account on the [unofficial forums][f],
|
||||
go to the "Projects" forum,
|
||||
and post your idea in a new topic there.
|
||||
|
||||
[f]: https://board.byuu.org/
|
||||
[f]: https://helmet.kafuka.org/bboard/
|
||||
|
674
GPLv3.txt
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
44
LICENSE.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
----------------------------------------------------------------------
|
||||
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.
|
||||
----------------------------------------------------------------------
|
@@ -27,11 +27,11 @@ Official higan resources
|
||||
------------------------
|
||||
|
||||
- [Official homepage](https://byuu.org/emulation/higan/)
|
||||
- [Official forum](https://board.byuu.org/viewforum.php?f=4)
|
||||
|
||||
Unofficial higan resources
|
||||
--------------------------
|
||||
|
||||
- [Unofficial forum](https://helmet.kafuka.org/bboard/)
|
||||
- Documentation for
|
||||
[the current stable version][stadocs]
|
||||
- [Source code repository](https://gitlab.com/higan/higan/)
|
||||
@@ -43,6 +43,6 @@ Unofficial higan resources
|
||||
[the latest WIP version][wipdocs]
|
||||
|
||||
|
||||
[wipwin]: https://gitlab.com/higan/higan/-/jobs/artifacts/master/download?job=windows-x86_64-binaries
|
||||
[wipwin]: https://gitlab.com/higan/higan/-/jobs/artifacts/master/download?job=higan-windows-x86_64-binaries
|
||||
[stadocs]: https://higan.readthedocs.io/
|
||||
[wipdocs]: https://higan.readthedocs.io/en/latest/
|
||||
|
297
README.txt
Normal file
@@ -0,0 +1,297 @@
|
||||
|
||||
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:
|
||||
|
||||
https://byuu.org/emulation/higan
|
||||
|
||||
Unofficial forum:
|
||||
|
||||
https://helmet.kafuka.org/bboard/
|
||||
|
||||
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.
|
@@ -8,5 +8,5 @@ checklink \
|
||||
--summary \
|
||||
--broken \
|
||||
--location=http://127.0.0.1:8000/ \
|
||||
--exclude 'github.com|board.byuu.org' \
|
||||
--exclude 'github.com|board.byuu.org|helmet.kafuka.org' \
|
||||
http://127.0.0.1:8000/
|
||||
|
@@ -8,7 +8,7 @@ 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`:
|
||||
|
||||
```text
|
||||
```python
|
||||
+- Super Famicom
|
||||
|
|
||||
+- dkc3.sfc
|
||||
@@ -27,7 +27,7 @@ For example,
|
||||
if another emulator loaded the game `dkc3.sfc`
|
||||
it might store the save data in `dkc3.srm`:
|
||||
|
||||
```text
|
||||
```python
|
||||
+- Super Famicom
|
||||
|
|
||||
+- dkc3.sfc
|
||||
@@ -53,7 +53,7 @@ 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]
|
||||
higan's [MSU1 feature][msu1]
|
||||
supports up to 99 audio tracks per game,
|
||||
and higan supports up to 133 save-states per game.
|
||||
Thus,
|
||||
@@ -70,7 +70,7 @@ 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
|
||||
[msu1]: ../guides/import.md#msu1-games
|
||||
[firmware]: ../guides/import.md#games-with-co-processor-firmware
|
||||
|
||||
For a more detailed motivation for game folders,
|
||||
@@ -128,19 +128,27 @@ to all emulators that support them:
|
||||
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].
|
||||
|
||||
Files used by higan's [MSU1 extension][msu1]
|
||||
are in the `msu1` sub-folder:
|
||||
|
||||
- `data.rom`:
|
||||
Holds data that the MSU1 can stream.
|
||||
- `track-*.pcm`:
|
||||
Holds streamable audio for [the MSU-1][msu1].
|
||||
Holds audio that the MSU1 can stream.
|
||||
|
||||
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)
|
||||
the [Cheat Editor](../interface/higan-tools.md#cheat-editor)
|
||||
is stored here.
|
||||
- `notes.txt`:
|
||||
Everything entered in the [Game Notes] 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.
|
||||
|
||||
[Game Notes]: ../interface/higan-tools.md#game-notes
|
||||
|
@@ -4,8 +4,8 @@ is the folder where all the
|
||||
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),
|
||||
When you use the items in
|
||||
[higan's Systems menu](../interface/higan.md#the-systems-menu),
|
||||
higan shows you the games for that console
|
||||
that are already in the library.
|
||||
|
||||
|
@@ -9,7 +9,7 @@ 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).
|
||||
and choosing [Manifest Viewer](../interface/higan-tools.md#manifest-viewer).
|
||||
|
||||
Why manifests?
|
||||
--------------
|
||||
@@ -17,7 +17,7 @@ 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,
|
||||
for emulators to guess the correct circuit board configuration,
|
||||
or at least
|
||||
to guess a *reasonable* configuration.
|
||||
However,
|
||||
@@ -100,6 +100,13 @@ heuristics will always be needed as a fallback,
|
||||
but at least if the heuristics are wrong
|
||||
they can be overridden.
|
||||
|
||||
If you are a homebrew author
|
||||
or have dumped a previously-unknown cartridge,
|
||||
and want to write a manifest yourself,
|
||||
you should read the [official manifest specification][manifest].
|
||||
|
||||
[manifest]: https://doc.byuu.org/higan/manifests/
|
||||
|
||||
Ignoring manifests
|
||||
------------------
|
||||
|
||||
|
@@ -119,7 +119,7 @@ Manager states
|
||||
--------------
|
||||
|
||||
higan's
|
||||
[State Manager](../interface/higan-tools.md#the-state-manager)
|
||||
[State Manager](../interface/higan-tools.md#state-manager)
|
||||
allows you to create over a hundred save states,
|
||||
and add a helpful description to each one.
|
||||
|
||||
|
63
docs/credits.md
Normal file
@@ -0,0 +1,63 @@
|
||||
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
|
||||
- ikari_01
|
||||
- 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://helmet.kafuka.org/byuubackup2/viewtopic.php@f=4&t=1631.html)
|
||||
on the archive of the official forums.
|
@@ -5,17 +5,17 @@ playing audio,
|
||||
and accepting input from game controllers.
|
||||
Or rather,
|
||||
there are many standards,
|
||||
and different ones work best
|
||||
and different ones work better
|
||||
on different computers.
|
||||
Therefore,
|
||||
higan comes with "drivers"
|
||||
for video, audio and input,
|
||||
so you can find the one that works best for you.
|
||||
so you can find the one that works best for your computer.
|
||||
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.
|
||||
go to the [Advanced tab] of the Settings window.
|
||||
|
||||
[Advanced tab]: ../interface/higan-settings.md#advanced
|
||||
|
||||
Here are the most notable drivers
|
||||
for each platform
|
||||
|
@@ -1,7 +1,6 @@
|
||||
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).
|
||||
be stored correctly in the [Game Library].
|
||||
For [regular games](#regular-games)
|
||||
this is simple,
|
||||
but some games require special treatment,
|
||||
@@ -11,7 +10,7 @@ unusual hardware.
|
||||
Regular games
|
||||
-------------
|
||||
|
||||
higan's importing tool, icarus, can import 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
|
||||
@@ -20,26 +19,20 @@ 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),
|
||||
open the [Systems menu],
|
||||
choose "Load ROM File ..."
|
||||
to open [a filesystem browser](../interface/common.md#the-filesystem-browser),
|
||||
to open a [filesystem browser],
|
||||
choose the ROM file of the game you want to play,
|
||||
and it will be imported into the library and start playing.
|
||||
and it will be imported into the library
|
||||
and (if possible) 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.
|
||||
run icarus directly.
|
||||
See the [icarus] documentation 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.)
|
||||
open the [Systems menu],
|
||||
then choose the appropriate console menu item.
|
||||
A filesystem browser will appear
|
||||
listing all the games in the library
|
||||
@@ -75,13 +68,24 @@ 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.
|
||||
most co-processor firmware cannot legally be distributed,
|
||||
so you'll need to
|
||||
obtain copies of the relevant firmware data
|
||||
for yourself.
|
||||
|
||||
To import a game that requires co-processor firmware,
|
||||
you must first combine the game data and the firmware into a single file.
|
||||
the easiest approach is to drop the firmware files into
|
||||
icarus' `Firmware` directory
|
||||
before importing the game.
|
||||
The directory should be beside the icarus executable,
|
||||
or it can be `%LOCALAPPDATA%\icarus\Firmware` (on Windows)
|
||||
or `~/.local/share/icarus/Firmware/` (on Linux).
|
||||
|
||||
If the easy approach doesn't work for a particular game,
|
||||
it may be because icarus has incorrectly guessed
|
||||
which firmware that game needs.
|
||||
To ensure icarus uses specific firmware with a specific game,
|
||||
you must 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`
|
||||
@@ -105,7 +109,7 @@ cat dsp1.program.rom dsp1.data.rom >> sbl2.sfc
|
||||
|
||||
**Note:**
|
||||
For co-processor chips with multiple firmware files,
|
||||
you must put the "program" file before the "data" file.
|
||||
always 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.
|
||||
@@ -127,13 +131,13 @@ here's the firmware files you'll need:
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">CX4</th>
|
||||
<th scope="row">CX4<br><sup>See Note 1</sup></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>
|
||||
<th scope="row" rowspan=2>DSP1/1A<br><sup>See Note 2</sup></th>
|
||||
<td><code>dsp1.data.rom</code></td>
|
||||
<td>2048</td>
|
||||
<td><code>0b5da6533e55852ee8fc397977ec5576c5b9f1fb2e05656d8f87123a121b076e</code></td>
|
||||
@@ -144,7 +148,7 @@ here's the firmware files you'll need:
|
||||
<td><code>269584b347a22953a2989494c850a7c1c027f4ca5add517a60e0c7d8833d0fac</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" rowspan=2>DSP1B<br><sup>See Note 2</sup></th>
|
||||
<th scope="row" rowspan=2>DSP1B<br><sup>See Note 3</sup></th>
|
||||
<td><code>dsp1b.data.rom</code></td>
|
||||
<td>2048</td>
|
||||
<td><code>8546cbac530830446bb8a277f6b139d4ad64d650bdbac7e4e150e2f095665049</code></td>
|
||||
@@ -213,53 +217,54 @@ here's the firmware files you'll need:
|
||||
<th scope="row" rowspan="2">ST018</th>
|
||||
<td><code>st018.data.rom</code></td>
|
||||
<td>32768</td>
|
||||
<td><code>b5377d1bebe8adc507a024b6e2b9b8fdf4877e451da84fbad05dff4e4a70311e</code></td>
|
||||
<td><code>c67032238b7182696cb80cf41b61bacda91adb2120b5370bea20c9dbf5cc79b8</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>st018.program.rom</code></td>
|
||||
<td>131072</td>
|
||||
<td><code>d90a5cda380e81cb9ba11a9da7539b173c49b31bedc7a3ac9c3c8b3f97e89e14</code></td>
|
||||
<td><code>6cceff3c6945bb2672040066d218efcd2f31492f3f5c28916c8e53435c2c887e</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**Note 1:**
|
||||
The CX4 firmware is shipped with higan,
|
||||
because it just contains mathematical tables
|
||||
and not a copyrightable program.
|
||||
|
||||
**Note 2:**
|
||||
The DSP1 and DSP1A are physically different,
|
||||
but the firmware inside is identical.
|
||||
|
||||
**Note 2:**
|
||||
**Note 3:**
|
||||
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,
|
||||
so if it cannot find your game in its 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,
|
||||
If you try to import a game with icarus,
|
||||
but it cannot find the required firmware files,
|
||||
a window will appear saying
|
||||
"Import completed, but with 1 errors. View log?"
|
||||
(or however many games were lacking the correct firmware).
|
||||
(or however many games were lacking 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
|
||||
> [sbl2.sfc] ROM image is missing data: dsp1.program.rom; dsp1.data.rom
|
||||
|
||||
If you try to import a game
|
||||
using the "Load ROM File ..." option
|
||||
in [the Library menu](../interface/higan.md#the-library-menu)
|
||||
using the "Load ROM File ..." option in the [Systems menu]
|
||||
but it does not include the correct firmware data,
|
||||
nothing will happen,
|
||||
and higan will just sit there
|
||||
with "No cartridge loaded" in
|
||||
with "Unloaded" in
|
||||
[the status bar](../interface/higan.md#the-status-bar).
|
||||
|
||||
Once a game with co-processor firmware is imported,
|
||||
@@ -268,7 +273,7 @@ you can play it just like any [regular game](#regular-games).
|
||||
Satellaview games
|
||||
-----------------
|
||||
|
||||
The [Satellaview][wpbsx]
|
||||
The [Satellaview]
|
||||
was a satellite modem peripheral
|
||||
released for the Super Famicom in Japan.
|
||||
As well as the actual modem
|
||||
@@ -282,7 +287,7 @@ This control cartridge was called
|
||||
which in English is
|
||||
*BS-X The Story of The Town Whose Name Was Stolen*.
|
||||
|
||||
[wpbsx]: https://en.wikipedia.org/wiki/Satellaview
|
||||
[Satellaview]: https://en.wikipedia.org/wiki/Satellaview
|
||||
|
||||
The control cartridge had a slot that accepted
|
||||
re-writable "memory paks",
|
||||
@@ -301,8 +306,16 @@ 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`
|
||||
To import a memory pak,
|
||||
you should use [icarus].
|
||||
You can use the "Load ROM File ..." menu item
|
||||
in the [Systems menu],
|
||||
but higan cannot actually load a memory pak directly,
|
||||
so once you choose a file to load
|
||||
it looks like nothing has happened.
|
||||
|
||||
When importing a memory pak,
|
||||
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
|
||||
@@ -317,8 +330,7 @@ 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)
|
||||
higan will open another [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,
|
||||
@@ -352,7 +364,7 @@ see [the BS-X Project](https://bsxproj.superfamicom.org/).
|
||||
Sufami Turbo games
|
||||
------------------
|
||||
|
||||
The [Sufami Turbo][wpst]
|
||||
The [Sufami Turbo]
|
||||
was a special cartridge released
|
||||
for the Super Famicom in Japan.
|
||||
The Sufami Turbo on its own does nothing,
|
||||
@@ -365,8 +377,16 @@ 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`
|
||||
To import a mini-cartridge,
|
||||
you should use [icarus].
|
||||
You can use the "Load ROM File ..." menu item
|
||||
in the [Systems menu],
|
||||
but higan cannot actually load a mini-cartridge directly,
|
||||
so once you choose a file to load
|
||||
it looks like nothing has happened.
|
||||
|
||||
When importing a mini-cartridge,
|
||||
the name of the file *must* end in `.st`
|
||||
(if it's in a `.zip` file,
|
||||
that's OK,
|
||||
but the name *inside* the `.zip` file
|
||||
@@ -380,8 +400,7 @@ 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)
|
||||
higan will open another [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,
|
||||
@@ -397,7 +416,12 @@ 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
|
||||
If you play Sufami Turbo games regularly,
|
||||
you may want to add the Sufami Turbo base cartridge
|
||||
to the [Systems menu]
|
||||
so you don't have to tell higan where it is every time.
|
||||
|
||||
[Sufami Turbo]: https://en.wikipedia.org/wiki/Sufami_Turbo
|
||||
|
||||
Super Game Boy games
|
||||
--------------------
|
||||
@@ -407,17 +431,29 @@ 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 2 was released in Japan,
|
||||
and had some minor extra features
|
||||
beyond the original Super Game Boy,
|
||||
but importing and playing games
|
||||
works the same way in higan.
|
||||
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 cartridge includes
|
||||
the complete hardware of an original
|
||||
(black-and-white)
|
||||
Game Boy,
|
||||
so it needs a boot ROM:
|
||||
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 emulates the Super Game Boy 2 completely,
|
||||
including the timing change.
|
||||
|
||||
Because the Super Game Boy cartridge includes
|
||||
the original Game Boy hardware,
|
||||
it needs a boot ROM.
|
||||
icarus includes these files
|
||||
and can reliably decide when to use them,
|
||||
so importing either Super Game Boy cartridge
|
||||
is just like [importing a regular game](#regular-games).
|
||||
|
||||
In case you need to check the Super Game Boy boot roms,
|
||||
here are their details:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
@@ -444,22 +480,19 @@ so it needs a boot ROM:
|
||||
</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)
|
||||
higan will open another [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.
|
||||
|
||||
If you regularly play Game Boy games
|
||||
through the Super Game Boy,
|
||||
you may want to add it to the [Systems menu]
|
||||
so you don't have to tell higan where it is every time.
|
||||
|
||||
**Note:**
|
||||
Only games for the original, black-and-white Game Boy
|
||||
can be used with the Super Game Boy.
|
||||
@@ -471,14 +504,14 @@ for details.
|
||||
|
||||
[blackcarts]: ../notes.md#playing-game-boy-color-games-in-game-boy-mode
|
||||
|
||||
MSU-1 games
|
||||
-----------
|
||||
MSU1 games
|
||||
----------
|
||||
|
||||
The MSU-1 is a fictional expansion chip
|
||||
The MSU1 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
|
||||
Although the MSU1 is not specific
|
||||
to any particular storage medium,
|
||||
it gives the Super Famicom similar capabilities
|
||||
to CD-based add-ons
|
||||
@@ -486,32 +519,32 @@ 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,
|
||||
Although the MSU1 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.
|
||||
The [SD2SNES] programmable cartridge
|
||||
even allows you to play MSU1 games on a real console.
|
||||
There are a number of homebrew games
|
||||
that make use of the MSU-1,
|
||||
that make use of the MSU1,
|
||||
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
|
||||
when importing an MSU1 game
|
||||
is that early firmware versions of the SD2SNES
|
||||
had a bug that caused MSU-1 music to play too quietly.
|
||||
had a bug that caused MSU1 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
|
||||
- If an MSU1 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.
|
||||
for the most popular MSU1 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,
|
||||
@@ -522,11 +555,11 @@ the short version is this:
|
||||
distorting and clipping,
|
||||
in which case try the "emulator" patch.
|
||||
|
||||
To import an MSU-1 game:
|
||||
To import an MSU1 game:
|
||||
|
||||
1. If you have a single, large file
|
||||
with the `.msu1` extension,
|
||||
that is a pack for use with [Mercurial Magic][mermag],
|
||||
that is a pack for use with [Mercurial Magic],
|
||||
which can automatically set up a game folder
|
||||
in the correct format.
|
||||
Go read Mercurial Magic's documentation
|
||||
@@ -534,29 +567,31 @@ To import an MSU-1 game:
|
||||
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,
|
||||
- If this is a homebrew game with MSU1 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,
|
||||
- If this is a commercial game modded for MSU1 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],
|
||||
apply the patch with a tool like [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)
|
||||
3. Find the game folder in the [game library]
|
||||
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`
|
||||
4. Inside the game folder,
|
||||
create a new folder named `msu1`.
|
||||
5. Copy the MSU1 data file into the new `msu1` folder.
|
||||
- This should be named `data.rom`
|
||||
- If there's no file by that name,
|
||||
look for a file with a `.msu` extension
|
||||
look for a file named `msu1.rom`,
|
||||
or 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.
|
||||
6. 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
|
||||
@@ -576,20 +611,20 @@ To import an MSU-1 game:
|
||||
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
|
||||
playing an MSU1 game is just like
|
||||
[a regular game](#regular-games).
|
||||
|
||||
[sd2snes]: https://sd2snes.de/
|
||||
[flips]: http://www.romhacking.net/utilities/1040/
|
||||
[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/
|
||||
[Mercurial Magic]: https://github.com/qwertymodo/Mercurial-Magic
|
||||
|
||||
Patched games
|
||||
-------------
|
||||
|
||||
The console emulation community
|
||||
has a long and vibrant history of game modding,
|
||||
or [ROM hacking][rhdn],
|
||||
or [ROM hacking],
|
||||
including fan-translations,
|
||||
new levels for existing games,
|
||||
and more.
|
||||
@@ -598,7 +633,7 @@ 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].
|
||||
like [Flips].
|
||||
|
||||
higan does not support soft-patching,
|
||||
so if you want to play a patched game in higan,
|
||||
@@ -608,7 +643,7 @@ 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/
|
||||
[ROM hacking]: http://www.romhacking.net/
|
||||
|
||||
Game Boy Advance games
|
||||
----------------------
|
||||
@@ -627,7 +662,7 @@ GBA games can be imported and played just like
|
||||
|
||||
Note that some GBA games
|
||||
have trouble with
|
||||
[in-game saves](../notes#in-game-saves-and-the-game-boy-advance).
|
||||
[in-game saves](../notes.md#in-game-saves-and-the-game-boy-advance).
|
||||
|
||||
PowerFest '94
|
||||
-------------
|
||||
@@ -645,79 +680,17 @@ 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.
|
||||
Previous versions of higan
|
||||
could emulate the PowerFest '94 cartridge,
|
||||
but changes to higan's manifest system in v107
|
||||
prevent PowerFest '94 from working in that version.
|
||||
Support will likely be re-added in a future version,
|
||||
but in the mean time you can use higan v106
|
||||
and follow [that version's import instructions][pf94v106].
|
||||
|
||||
You will need the following files:
|
||||
[pf94v106]: https://higan.readthedocs.io/en/v106/guides/import/#powerfest-94
|
||||
|
||||
<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.
|
||||
[filesystem browser]: ../interface/common.md#the-filesystem-browser
|
||||
[Game Library]: ../concepts/game-library.md
|
||||
[icarus]: ../interface/icarus.md
|
||||
[Systems menu]: ../interface/higan.md#the-systems-menu
|
||||
|
@@ -2,17 +2,16 @@ 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
|
||||
Video shaders customise how the emulated 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).
|
||||
the "Shader" sub-menu of the [Settings menu].
|
||||
Which shaders are available depends on
|
||||
the [video driver](drivers.md#video) higan is configured to use.
|
||||
the [video driver] higan is configured to use.
|
||||
Most drivers only support these shaders:
|
||||
|
||||
- **None**
|
||||
@@ -20,7 +19,8 @@ Most drivers only support these shaders:
|
||||
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],
|
||||
- If you enable Scale, Stretch, or Aspect Correction modes
|
||||
in the Output sub-menu of the [Settings menu],
|
||||
neighbouring console pixels may be drawn
|
||||
with a different number of computer pixels due to rounding errors,
|
||||
causing a distracting rippling effect.
|
||||
@@ -31,8 +31,6 @@ Most drivers only support these shaders:
|
||||
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.
|
||||
|
||||
@@ -52,12 +50,13 @@ 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 get them from the [unofficial higan repository].
|
||||
- [quark-shaders] contains many high-quality shaders for use with higan.
|
||||
- You can write your own.
|
||||
|
||||
[unofficial higan repository]: https://gitlab.com/higan/higan/tree/master/shaders
|
||||
[quark-shaders]: https://github.com/hizzlekizzle/quark-shaders
|
||||
|
||||
How to install custom shaders
|
||||
-----------------------------
|
||||
|
||||
@@ -68,11 +67,11 @@ it should contain a file named `manifest.bml`,
|
||||
and probably some `*.fs` or `*.vs` files.
|
||||
|
||||
Place the shader folder inside
|
||||
the `Video Shaders` folder
|
||||
the `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`.
|
||||
If you don't have a `shaders` folder,
|
||||
create it beside the `systems` folder
|
||||
and `settings.bml`.
|
||||
|
||||
- On Windows,
|
||||
this is probably the folder containing `higan.exe`
|
||||
@@ -80,10 +79,9 @@ like `Game Boy Advance.sys` and `Super Famicom.sys`.
|
||||
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.
|
||||
open the [Settings menu],
|
||||
and choose "Advanced ..." to open
|
||||
the [Advanced tab] of the Settings window.
|
||||
Under "Driver Selection",
|
||||
make sure "Video" is set to "OpenGL".
|
||||
If it wasn't already set that way,
|
||||
@@ -91,7 +89,7 @@ you'll need to restart higan
|
||||
for the change to take effect.
|
||||
|
||||
Open the Settings menu again,
|
||||
choose the "Video Shader" sub-menu,
|
||||
choose the "Shader" sub-menu,
|
||||
and now the shaders you installed
|
||||
should be listed at the bottom of the menu.
|
||||
|
||||
@@ -213,3 +211,7 @@ 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.
|
||||
|
||||
[Settings menu]: ../interface/higan.md#the-settings-menu
|
||||
[video driver]: drivers.md#video
|
||||
[Advanced tab]: ../interface/higan-settings.md#advanced
|
||||
|
@@ -1,7 +1,7 @@
|
||||
higan, the multi-system emulator
|
||||
================================
|
||||
|
||||
higan emulates a number of classic video-game consoles of the 1980s and 1990s,
|
||||
higan emulates a number of 2D video-game consoles,
|
||||
allowing you to play classic games on a modern general-purpose computer.
|
||||
|
||||
To get started with higan right away,
|
||||
@@ -11,7 +11,7 @@ see the [Quick Start](qs.md) section of the documentation.
|
||||
About higan
|
||||
-----------
|
||||
|
||||
As of v104,
|
||||
As of v107,
|
||||
higan has top-tier support for the following consoles:
|
||||
|
||||
- Nintendo Super Famicom/Super Nintendo Entertainment System,
|
||||
@@ -33,6 +33,7 @@ It also includes some level of support for these consoles:
|
||||
- NEC SuperGrafx
|
||||
- Bandai WonderSwan
|
||||
- Bandai WonderSwan Color
|
||||
- Pocket Challenge v2
|
||||
|
||||
**Note:** Some consoles were released under different names
|
||||
in different geographic regions.
|
||||
@@ -63,17 +64,17 @@ 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.
|
||||
post a message on the unofficial forum.
|
||||
|
||||
Official higan resources
|
||||
------------------------
|
||||
|
||||
- [Official homepage](https://byuu.org/emulation/higan/)
|
||||
- [Official forum](https://board.byuu.org/viewforum.php?f=4)
|
||||
|
||||
Unofficial higan resources
|
||||
--------------------------
|
||||
|
||||
- [Unofficial forum](https://helmet.kafuka.org/bboard/)
|
||||
- [Source code repository](https://gitlab.com/higan/higan/)
|
||||
archives official higan releases
|
||||
and WIP snapshots
|
||||
@@ -84,12 +85,12 @@ Unofficial higan resources
|
||||
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
|
||||
is a tool for converting MSU1 games and mods into a format
|
||||
higan can use.
|
||||
See [Importing MSU-1 games][msu1] for details.
|
||||
See [Importing MSU1 games][msu1] for details.
|
||||
|
||||
[shaders]: guides/shaders.md
|
||||
[msu1]: guides/import.md#msu-1-games
|
||||
[msu1]: guides/import.md#msu1-games
|
||||
|
||||
There are also other projects
|
||||
based on current or older versions of higan,
|
||||
@@ -112,7 +113,7 @@ that you might want to check out.
|
||||
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)
|
||||
- [nSide](https://gitlab.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.
|
||||
|
@@ -23,7 +23,7 @@ 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,
|
||||
The correct GBA BIOS file is exactly 16,384 bytes long,
|
||||
and has the SHA256 hash
|
||||
`fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570`.
|
||||
|
||||
|
@@ -12,7 +12,7 @@ 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,
|
||||
You will also need GCC 7 or higher,
|
||||
including the C and C++ compiler,
|
||||
GNU Make,
|
||||
and development files
|
||||
@@ -24,7 +24,7 @@ for the following libraries:
|
||||
- Mesa
|
||||
- gtksourceview 2.x
|
||||
- Cairo
|
||||
- SDL 1.2
|
||||
- SDL 2.0
|
||||
- libXv
|
||||
- libAO
|
||||
- OpenAL
|
||||
@@ -35,7 +35,7 @@ On a Debian-derived Linux distribution
|
||||
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 \
|
||||
mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl2-dev \
|
||||
libxv-dev libao-dev libopenal-dev libudev-dev
|
||||
|
||||
Once you have all the dependencies installed,
|
||||
@@ -53,9 +53,9 @@ being installed system-wide.
|
||||
3. Type `cd ~/higan-src`
|
||||
(or wherever you put the higan source)
|
||||
and press Enter
|
||||
4. Type `make -C higan compiler=g++` and press Enter
|
||||
4. Type `make -C higan target=higan` and press Enter
|
||||
to build the main higan executable
|
||||
5. Type `make -C icarus compiler=g++` and press Enter
|
||||
5. Type `make -C icarus` and press Enter
|
||||
to build the icarus import tool
|
||||
|
||||
Installing a compiled build on Linux
|
||||
@@ -68,7 +68,7 @@ as described in the previous section:
|
||||
2. Type `cd ~/higan-src`
|
||||
(or wherever you put the higan source)
|
||||
and press Enter
|
||||
3. Type `make -C higan install` and press Enter
|
||||
3. Type `make -C higan target=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
|
||||
@@ -123,7 +123,7 @@ as installed by the above instructions:
|
||||
2. Type `cd ~/higan-src`
|
||||
(or wherever you put the higan source)
|
||||
and press Enter
|
||||
3. Type `make -C higan uninstall` and press Enter
|
||||
3. Type `make -C higan target=higan uninstall` and press Enter
|
||||
4. Type `make -C icarus uninstall` and press Enter
|
||||
|
||||
To remove higan's configuration,
|
||||
|
@@ -50,38 +50,58 @@ 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.
|
||||
You will need a C++ compiler that supports C++17 to compile higan.
|
||||
We recommend installing the latest version of [MinGW-W64].
|
||||
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).
|
||||
[MinGW-W64]: https://mingw-w64.org/
|
||||
|
||||
Once you've installed the compiler,
|
||||
open a command-prompt window,
|
||||
MinGW-W64 is available in a number of variants,
|
||||
and the installer should ask you which you want.
|
||||
|
||||
- **Version:**
|
||||
Version 8.1.0 is known to work,
|
||||
later versions may work too.
|
||||
- **Architecture:**
|
||||
You *must* choose "x86_64", not "i686".
|
||||
When built with an i686 compiler,
|
||||
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 archive of the official forum
|
||||
[for](https://helmet.kafuka.org/byuubackup2/viewtopic.php@f=4&t=1636&start=20.html#p41977)
|
||||
[details](https://helmet.kafuka.org/byuubackup2/viewtopic.php@f=4&t=1636&start=30.html#p42253).
|
||||
- **Threads:**
|
||||
Both options should work,
|
||||
but higan is developed with the "posix" model.
|
||||
- **Exception:**
|
||||
You *must* choose "seh",
|
||||
or higan may crash at startup.
|
||||
If "seh" is not an option,
|
||||
make sure "Architecture" is set to "x86_64".
|
||||
- **Build Revision:**
|
||||
Choose the largest number, whatever it is.
|
||||
|
||||
When the compiler is installed,
|
||||
it adds a "Run terminal" shortcut to the Start menu
|
||||
which opens a command-prompt
|
||||
with all the compiler tools available.
|
||||
|
||||
To verify that the compiler is installed correctly,
|
||||
launch the "Run Terminal" shortcut,
|
||||
type `g++ --version`
|
||||
then press Enter
|
||||
to check it's installed correctly.
|
||||
then press Enter.
|
||||
You should see a message like
|
||||
|
||||
```text
|
||||
g++ 1.2.3 20010101
|
||||
Copyright (C) 2001 Free Software Foundation, Inc.
|
||||
g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
|
||||
Copyright (C) 2018 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.
|
||||
```
|
||||
@@ -91,11 +111,11 @@ 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%`.
|
||||
make sure you're using the "Run terminal" shortcut,
|
||||
or otherwise have MinGW-W64's "bin" directory in your `%PATH%`.
|
||||
See the compiler's documentation for help with that.
|
||||
|
||||
Once mingw is installed and available from the command prompt:
|
||||
Once the compiler is installed:
|
||||
|
||||
1. Put the higan source code in some convenient location,
|
||||
like `C:\higan-src`
|
||||
@@ -103,13 +123,11 @@ Once mingw is installed and available from 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 compiler=g++` and press Enter
|
||||
4. Type `mingw32-make -C icarus` and press Enter
|
||||
to build the icarus import tool
|
||||
5. Type `mingw32-make -C higan compiler=g++` and press Enter
|
||||
5. Type `mingw32-make -C higan target=higan` and press Enter
|
||||
to build the main higan executable
|
||||
|
||||
[tdm]: http://tdm-gcc.tdragon.net/download
|
||||
|
||||
Installing a compiled build on Windows
|
||||
--------------------------------------
|
||||
|
||||
@@ -120,24 +138,20 @@ Installing a compiled build on Windows
|
||||
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`
|
||||
4. Copy `C:\higan-src\icarus\Firmware` and its contents
|
||||
into the new folder
|
||||
5. Copy all the `*.sys` folders
|
||||
in `C:\higan-src\higan\systems`
|
||||
5. Copy `C:\higan-src\higan\out\higan.exe`
|
||||
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.
|
||||
6. Copy `C:\higan-src\higan\systems`
|
||||
into the new folder
|
||||
7. If the higan source includes a `shaders` folder,
|
||||
copy it into the new folder too.
|
||||
|
||||
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.
|
||||
and folders named `Database`, `Firmware`, `systems`,
|
||||
and possibly `shaders`.
|
||||
This is what you would get by downloading an official build,
|
||||
as described under
|
||||
[Installing an official release on Windows][instwin]
|
||||
|
1
docs/interface/down.png
Symbolic link
@@ -0,0 +1 @@
|
||||
../../hiro/resource/icon/go/down.png
|
@@ -57,7 +57,8 @@ higan will prompt for one.
|
||||
`SUBGAME2` is ignored.
|
||||
|
||||
When `GAME` refers to
|
||||
the [Super Game Boy](../guides/import.md#super-game-boy-games),
|
||||
the [Super Game Boy](../guides/import.md#super-game-boy-games)
|
||||
or Super Game Boy 2,
|
||||
`SUBGAME1` should be
|
||||
the path to a game folder or ROM file
|
||||
representing a Game Boy game to insert into the slot.
|
||||
|
@@ -6,7 +6,79 @@ 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:
|
||||
This window has a tab for each main category of options.
|
||||
|
||||
Systems
|
||||
=======
|
||||
|
||||
This tab configures the contents of
|
||||
[the Systems menu](higan.md#the-systems-menu),
|
||||
so you can make it easier to load the games you care about
|
||||
and hide things that get in the way.
|
||||
|
||||
Each item in the list represents
|
||||
a single item in the Systems menu.
|
||||
If the box at the left is ticked,
|
||||
that item will be included in the menu,
|
||||
otherwise it will be hidden—but higan will remember its configuration
|
||||
in case you want to show it again.
|
||||
|
||||
At the bottom left are
|
||||
 and  buttons.
|
||||
These move the selected item
|
||||
upward or downward in the list.
|
||||
|
||||
The **Append** button in the lower right
|
||||
adds a new item to the end of the list.
|
||||
It opens the [System Properties](#system-properties) dialog,
|
||||
so you can enter the details of the new item.
|
||||
If you don't want the new item to be at the end,
|
||||
you can use the up and down buttons
|
||||
in the lower left
|
||||
to move it to its intended location.
|
||||
|
||||
The **Modify** button in the lower right
|
||||
opens the [System Properties](#system-properties) dialog
|
||||
for the selected item,
|
||||
so you can make changes.
|
||||
|
||||
The **Remove** button in the lower right
|
||||
removes the selected item from the list entirely.
|
||||
Unlike hiding the item,
|
||||
this forgets whatever configuration the item had.
|
||||
|
||||
System Properties
|
||||
-----------------
|
||||
|
||||
This dialog appears when clicking "Append" or "Modify"
|
||||
in the [Systems](#systems) tab.
|
||||
It allows you to configure a new ("Append") or existing ("Modify") entry
|
||||
in the [Systems menu](higan.md#the-systems-menu).
|
||||
|
||||
- **System** controls which console will be emulated
|
||||
when this menu-item is chosen.
|
||||
- **Load** controls what game will be loaded
|
||||
into the emulated console
|
||||
when this menu-item is chosen.
|
||||
- If left blank,
|
||||
higan will open [a filesystem browser](common.md#the-filesystem-browser)
|
||||
allowing you to pick a previously-imported game from
|
||||
the [game library](../concepts/game-library.md).
|
||||
- If you choose a particular game,
|
||||
higan will immediately load it
|
||||
when the menu-item is chosen.
|
||||
If the game requires additional data
|
||||
(for example, the Super Game Boy requires a Game Boy cartridge)
|
||||
higan will prompt for it.
|
||||
- **Alias** controls the name of this item,
|
||||
as displayed in the Systems menu.
|
||||
- **Append** (present in "Append" mode) closes the dialog
|
||||
and adds a new item with this configuration
|
||||
to the list.
|
||||
- **Modify** (present in "Modify" mode) closes the dialog
|
||||
and updates the configuration of
|
||||
the item being modified.
|
||||
- **Cancel** closes the dialog without making any changes.
|
||||
|
||||
Video
|
||||
=====
|
||||
@@ -20,9 +92,9 @@ 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,
|
||||
where 0% makes things black-and-white,
|
||||
100% is normal,
|
||||
and 200% is garishly brightly coloured.
|
||||
and 200% is garishly exaggerated colour.
|
||||
- **Gamma** adjusts how bright mid-range colours are
|
||||
compared to the brightest colours,
|
||||
where 100% is normal,
|
||||
@@ -30,20 +102,23 @@ of the emulated console's video output:
|
||||
This is in addition to
|
||||
any adjustment applied by
|
||||
the "Colors" option
|
||||
in the "Video Emulation" sub-menu
|
||||
in the "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.
|
||||
**Overscan Area**
|
||||
controls what parts of the video output are hidden
|
||||
when "Show Overscan Area" is disabled
|
||||
in the "Output" sub-menu of
|
||||
the [Settings menu](higan.md#the-settings-menu).
|
||||
On a standard-definition television,
|
||||
the outermost edges of the emulated console's video output
|
||||
would have been hidden by the bezel,
|
||||
so some games (particularly on the Famicom)
|
||||
allowed random glitchy output to appear there,
|
||||
assuming it wouldn't be visible.
|
||||
|
||||
- **Horizontal**
|
||||
removes pixels from the left and right of the video output.
|
||||
@@ -58,41 +133,7 @@ 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**
|
||||
**Fullscreen**
|
||||
settings apply
|
||||
when higan is running fullscreen,
|
||||
because it was started with the `--fullscreen`
|
||||
@@ -100,10 +141,6 @@ because it was started with the `--fullscreen`
|
||||
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
|
||||
@@ -113,7 +150,7 @@ the Toggle Fullscreen [hotkey](higan-settings.md#hotkeys).
|
||||
from drawing anything,
|
||||
and may also temporarily disable any kind of compositing,
|
||||
reducing video latency.
|
||||
As of v104,
|
||||
As of v107,
|
||||
only the Direct3D video driver is capable of exclusive mode;
|
||||
with other drivers this option does nothing.
|
||||
|
||||
@@ -164,52 +201,52 @@ before it is sent to your computer's 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.
|
||||
This tab controls
|
||||
how higan handles input for the emulated consoles.
|
||||
|
||||
**When focus is lost**
|
||||
controls what happens when a game is loaded,
|
||||
but higan is not the current foreground window.
|
||||
|
||||
- **Pause Emulation** automatically pauses emulation.
|
||||
- **Block Input** allows emulation to keep running,
|
||||
but higan will ignore all configured button presses.
|
||||
If you're using the keyboard to emulate a controller,
|
||||
this prevents typing in other applications
|
||||
from messing with higan,
|
||||
but music will keep playing.
|
||||
- **Allow Input** allows emulation to continue as normal.
|
||||
This allows somebody to play higan with a controller
|
||||
in one window,
|
||||
while somebody else types into another application
|
||||
in another window.
|
||||
|
||||
The rest of this tab configures
|
||||
the mapping from PC inputs to emulated controllers.
|
||||
The exact PC inputs that can be mapped
|
||||
depend on [the input driver](../guides/drivers.md#input).
|
||||
|
||||
General input settings:
|
||||
To choose which of the possible controllers to configure:
|
||||
|
||||
- **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!
|
||||
- The first drop-down list controls
|
||||
which console's ports appear in the second list.
|
||||
- The second drop-down list controls
|
||||
which port's compatible controllers appear in the third list.
|
||||
- The third drop-down list controls
|
||||
which controller's inputs are shown
|
||||
in the mapping list below.
|
||||
|
||||
Choosing which of the possible controllers to configure:
|
||||
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.
|
||||
|
||||
- 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:
|
||||
To configure the selected controller:
|
||||
|
||||
- The mapping list includes
|
||||
every button and axis on the selected controller,
|
||||
@@ -260,6 +297,13 @@ then click one of the
|
||||
or "Mouse Y-axis"
|
||||
buttons in the bottom-left of the window.
|
||||
|
||||
**Note:**
|
||||
To use an controller axis mapped to a mouse axis,
|
||||
higan will need to be in fullscreen mode,
|
||||
or you'll need to press
|
||||
the key mapped to "Toggle Mouse Capture"
|
||||
on the [Hotkeys tab](#hotkeys).
|
||||
|
||||
If you start mapping a button or axis,
|
||||
but decide you don't want to,
|
||||
you can press Escape
|
||||
@@ -267,10 +311,25 @@ to exit the "Press a key or button to map..." mode
|
||||
without actually mapping anything.
|
||||
|
||||
**Note:**
|
||||
Consoles in the Game Boy family include
|
||||
The Game Boy and Game Boy Color consoles
|
||||
have a "Cartridge" port with controllers
|
||||
that are not really controllers:
|
||||
|
||||
- The "MBC5" controller is automatically used for
|
||||
games whose cartridge includes the MBC5 memory-mapper
|
||||
and a rumble motor,
|
||||
like *Pokémon Pinball*.
|
||||
See [Rumble Compatibility for Game Boy (Color)][gbcrumble]
|
||||
for details.
|
||||
- The "MBC7" controller is automatically used for
|
||||
games whose cartridge includes the MBC7 memory-mapper
|
||||
and an accelerometer,
|
||||
like *Kirby Tilt 'n' Tumble*.
|
||||
|
||||
**Note:**
|
||||
The Game Boy Advance console includes
|
||||
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]
|
||||
See [Rumble Compatibility for Game Boy Advance][gbarumble]
|
||||
for details.
|
||||
|
||||
[gbcrumble]: ../notes.md#rumble-compatibility-for-game-boy-color
|
||||
@@ -288,12 +347,12 @@ Hotkeys
|
||||
|
||||
This tab is like "Inputs" above,
|
||||
except it contains controls for higan itself
|
||||
instead of the emulated console.
|
||||
instead of for 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.
|
||||
can 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.
|
||||
@@ -308,13 +367,22 @@ instead of the emulated console.
|
||||
- **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.
|
||||
until the Pause Emulation hotkey is pressed a second time,
|
||||
or "Pause Emulation" is chosen from
|
||||
[the Tools menu](higan.md#the-tools-menu)..
|
||||
- **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.
|
||||
- **Soft Reset** restarts the emulated console's CPU
|
||||
while leaving the console's memory untouched,
|
||||
just like the "Soft Reset" menu item
|
||||
in [the console menu](higan.md#the-console-menu).
|
||||
This hotkey does nothing
|
||||
when the "Soft Reset" item
|
||||
does not appear in the console menu.
|
||||
- **Power Cycle** turns the emulated console off and back on
|
||||
(a "hard reset"),
|
||||
just like the "Power Cycle" menu item
|
||||
@@ -359,8 +427,7 @@ for help choosing which drivers you should use.
|
||||
configures how higan interacts
|
||||
with the [Game Library](../concepts/game-library.md).
|
||||
|
||||
- **Location** selects where higan
|
||||
looks for games to load.
|
||||
- **Location** tells higan where to look 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
|
||||
@@ -371,3 +438,16 @@ with the [Game Library](../concepts/game-library.md).
|
||||
to guess a manifest on the fly.
|
||||
See [Ignoring manifests](../concepts/manifests.md#ignoring-manifests)
|
||||
for details.
|
||||
|
||||
**Other**
|
||||
|
||||
- **Auto-Save Memory Periodically** makes higan write
|
||||
[in-game saves](../concepts/save-states.md#save-states-versus-in-game-saves)
|
||||
to disk during gameplay,
|
||||
instead of only when higan exits.
|
||||
This may cause stuttering,
|
||||
but means that you haven't lost everything
|
||||
if higan crashes,
|
||||
or your computer loses power.
|
||||
- Note that this does not include
|
||||
[game notes](higan-tools.md#game-notes)
|
||||
|
@@ -3,10 +3,10 @@ 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 window has a tab for each tool.
|
||||
|
||||
The Cheat Editor
|
||||
----------------
|
||||
Cheat Editor
|
||||
============
|
||||
|
||||
For some consoles,
|
||||
higan supports applying temporary changes to the code of a running game.
|
||||
@@ -89,8 +89,8 @@ in Super Mario World,
|
||||
you can lock the time to 999 with these codes:
|
||||
`7e0f31=09+7e0f32=09+7e0f33=09`.
|
||||
|
||||
The State Manager
|
||||
-----------------
|
||||
State Manager
|
||||
=============
|
||||
|
||||
The State Manager allows you to create,
|
||||
load,
|
||||
@@ -125,8 +125,8 @@ 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
|
||||
-------------------
|
||||
Manifest Viewer
|
||||
===============
|
||||
|
||||
As described in
|
||||
[Game Manifests](../concepts/manifests.md),
|
||||
@@ -135,3 +135,14 @@ 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.
|
||||
|
||||
Game Notes
|
||||
==========
|
||||
|
||||
The Game Notes tab
|
||||
is a place where you can write whatever you want
|
||||
about the running game.
|
||||
This information is automatically stored inside
|
||||
the [game folder](../concepts/game-folders.md)
|
||||
and loaded back into this tab
|
||||
every time the game is loaded.
|
||||
|
@@ -5,27 +5,31 @@ a status-bar across the bottom,
|
||||
and a large area in the middle that shows
|
||||
the running game's video output.
|
||||
|
||||
The Library menu
|
||||
The Systems menu
|
||||
----------------
|
||||
|
||||
Manufacturer sub-menus
|
||||
allow you to play
|
||||
games you've already imported
|
||||
into higan's
|
||||
[game library](../concepts/game-library.md).
|
||||
This menu lists the systems higan emulates.
|
||||
Choosing any system from this menu allows you to play
|
||||
games for that system that you've already imported
|
||||
into higan's [game library](../concepts/game-library.md).
|
||||
See [Importing and playing games](../guides/import.md).
|
||||
|
||||
|
||||
You can customise this menu
|
||||
in [higan's Systems settings](higan-settings.md#systems)
|
||||
to hide systems you don't care about,
|
||||
or add a specific cartridge for any supported system.
|
||||
This makes it more convenient
|
||||
to play games that involve mini-cartridges:
|
||||
for example, you can
|
||||
add the Sufami Turbo to the list
|
||||
and load *SD Ultra Battle*
|
||||
in two clicks instead of three.
|
||||
|
||||
**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
|
||||
---------------
|
||||
@@ -35,8 +39,7 @@ 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.
|
||||
it's named for the console that runs the loaded game.
|
||||
For example,
|
||||
when playing a Game Boy game,
|
||||
you will have a "Game Boy" menu.
|
||||
@@ -46,7 +49,6 @@ 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
|
||||
@@ -76,7 +78,21 @@ 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**
|
||||
is like "Controller Port 1"
|
||||
for consoles that only have one controller port.
|
||||
|
||||
**Hardware**
|
||||
appears for consoles with buttons on the main unit,
|
||||
like the Game Boy,
|
||||
or Master System.
|
||||
It only allows the built-in controls to be used.
|
||||
|
||||
**Cartridge**
|
||||
appears for the Game Boy and Game Boy Colour.
|
||||
The options inside it do nothing.
|
||||
|
||||
**Expansion Port**
|
||||
allows you
|
||||
to connect different emulated devices
|
||||
@@ -91,6 +107,30 @@ This option allows the same program
|
||||
to control the emulated SNES,
|
||||
for development or testing.
|
||||
|
||||
**Extension Port**
|
||||
is the name the Sega Mega Drive used for its expansion port.
|
||||
|
||||
**Soft Reset**
|
||||
restarts the emulated console's CPU
|
||||
while leaving the console's memory untouched,
|
||||
like pressing the "reset" button
|
||||
on a physical console.
|
||||
|
||||
This menu item does not appear
|
||||
for consoles that did not have a "reset" button,
|
||||
like hand-helds.
|
||||
|
||||
It also does not appear for the Sega Master System,
|
||||
since that console's reset button is wired up like a controller
|
||||
rather than directly attached to the CPU.
|
||||
To reset the Master System,
|
||||
bind a keyboard or joypad button
|
||||
to the "Reset" function
|
||||
on the "Controls" controller
|
||||
in the "Hardware" port
|
||||
of the Sega Master System
|
||||
in [higan's Input settings](higan-settings.md#input).
|
||||
|
||||
**Power Cycle**
|
||||
restarts the loaded game
|
||||
as though the emulated console were switched off and on again.
|
||||
@@ -99,7 +139,7 @@ as though the emulated console were switched off and on again.
|
||||
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).
|
||||
from [the Systems menu](#the-systems-menu).
|
||||
|
||||
[21fx]: https://github.com/defparam/21FX
|
||||
|
||||
@@ -109,13 +149,83 @@ The Settings menu
|
||||
The Settings menu allows you to configure things
|
||||
that aren't specific to any particular console.
|
||||
|
||||
|
||||
**Video Scale** determines the size
|
||||
**Size**
|
||||
determines the size
|
||||
of the emulated console's video output
|
||||
when higan is running in windowed mode
|
||||
(as opposed to fullscreen).
|
||||
The menu-items that indicate particular sizes
|
||||
are only approximate, since
|
||||
aspect correction can be applied,
|
||||
different consoles have different native image sizes,
|
||||
and some consoles can change the size of their output image dynamically.
|
||||
|
||||
**Video Emulation** applies various effects
|
||||
- **1x (240p)**
|
||||
resizes the higan window
|
||||
so that each pixel of the emulated console's video output
|
||||
is drawn as a single pixel on the computer screen.
|
||||
- **2x (480p)**
|
||||
resizes the higan window
|
||||
so that each pixel of the emulated console's video output
|
||||
is drawn as a 2×2 block of pixels on the computer screen.
|
||||
- **3x (720p)**
|
||||
resizes the higan window
|
||||
so that each pixel of the emulated console's video output
|
||||
is drawn as a 3×3 block of pixels on the computer screen.
|
||||
- **Shrink Window To Size**
|
||||
resizes the higan window to fit the emulated console's video output
|
||||
at its current scale,
|
||||
so there's no black padding between the image and the window border
|
||||
(some padding may remain
|
||||
if "Show Overscan Area" is enabled
|
||||
in the Output menu).
|
||||
- **Center Window**
|
||||
moves the higan window to the centre of the computer screen.
|
||||
|
||||
**Output**
|
||||
controls how higan draws the emulated console's video output
|
||||
into the space available,
|
||||
in both windowed and fullscreen modes.
|
||||
|
||||
- **Center**
|
||||
draws the emulated video
|
||||
at the largest integer multiple of the native size that will fit,
|
||||
centered in the space available.
|
||||
This gives the most crisp output,
|
||||
but often has black borders.
|
||||
- **Scale**
|
||||
draws the emulated video
|
||||
at the largest size that will fit,
|
||||
and which preserves the image's aspect ratio.
|
||||
This strikes a balance between
|
||||
displaying the video output as it was intended,
|
||||
and eliminating black borders.
|
||||
- **Stretch**
|
||||
draws the emulated video
|
||||
to cover the entire available output area,
|
||||
even if that distorts the image.
|
||||
This completely eliminates black borders,
|
||||
but can look very weird.
|
||||
- **Adaptive Sizing**
|
||||
allows higan to resize its window
|
||||
when the emulated console changes the resolution
|
||||
of its video output.
|
||||
This can avoid black borders,
|
||||
but the window resizing itself might be even more distracting.
|
||||
- **Aspect Correction**
|
||||
horizontally stretches the emulated video output
|
||||
to match the aspect ratio produced by the original console.
|
||||
It can make the output look more "lumpy",
|
||||
but is a more accurate representation
|
||||
of the original console's output.
|
||||
- **Show Overscan Area**
|
||||
controls whether the area defined by
|
||||
the "Overscan Area" sliders in
|
||||
the [Video settings](higan-settings.md#video)
|
||||
is clipped from the emulated video output
|
||||
or shown.
|
||||
|
||||
**Emulation** applies various effects
|
||||
to the emulated console's video output
|
||||
to reproduce some behaviours
|
||||
that aren't technically part of the console itself:
|
||||
@@ -140,8 +250,8 @@ that aren't technically part of the console itself:
|
||||
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
|
||||
|
||||
**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)
|
||||
@@ -166,6 +276,9 @@ at the bottom of the window.
|
||||
This option has no effect in fullscreen mode.
|
||||
See [The status bar](#the-status-bar) for more information.
|
||||
|
||||
**Systems ...**
|
||||
opens [higan's Systems settings](higan-settings.md#systems).
|
||||
|
||||
**Video ...**
|
||||
opens [higan's Video settings](higan-settings.md#video).
|
||||
|
||||
@@ -199,16 +312,26 @@ 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)
|
||||
**Pause Emulation**
|
||||
pauses the emulated console
|
||||
until this menu-item is selected again.
|
||||
This can also be triggered by
|
||||
the [pause hotkey](higan-settings.md#hotkeys).
|
||||
|
||||
**Cheat Editor ...**
|
||||
opens the [Cheat Editor tab](higan-tools.md#cheat-editor)
|
||||
of the Tools window.
|
||||
|
||||
**State Manager**
|
||||
opens [the State Manager tab](higan-tools.md#the-state-manager)
|
||||
**State Manager ...**
|
||||
opens the [State Manager tab](higan-tools.md#state-manager)
|
||||
of the Tools window.
|
||||
|
||||
**Manifest Viewer**
|
||||
opens [the Manifest Viewer tab](higan-tools.md#the-manifest-viewer)
|
||||
**Manifest Viewer ...**
|
||||
opens the [Manifest Viewer tab](higan-tools.md#manifest-viewer)
|
||||
of the Tools window.
|
||||
|
||||
**Game Notes ...**
|
||||
opens [the Game Notes tab](higan-tools.md#game-notes)
|
||||
of the Tools window.
|
||||
|
||||
The Help menu
|
||||
@@ -237,7 +360,7 @@ 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".
|
||||
the status bar displays "Unloaded".
|
||||
|
||||
When a game is loaded and running,
|
||||
the status bar displays the current emulation speed
|
||||
@@ -255,11 +378,13 @@ 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),
|
||||
selected "Pause Emulation" from [the Tools menu](#the-tools-menu),
|
||||
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,
|
||||
select "Pause Emulation" from the Tools menu again,
|
||||
and/or press the "pause" hotkey.
|
||||
|
||||
The status bar briefly displays "Selected quick state slot X"
|
||||
@@ -279,7 +404,7 @@ 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"
|
||||
The status bar briefly displays "System has been power cycled"
|
||||
when you choose "Power Cycle" from [the console menu](#the-console-menu),
|
||||
or press the "Power Cycle" hotkey.
|
||||
|
||||
|
@@ -1,10 +1,7 @@
|
||||
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.
|
||||
icarus is a separate tool
|
||||
bundled with higan
|
||||
that allows you to bulk-import ROM files
|
||||
into higan's [game library].
|
||||
|
||||
Bulk importing ROM files
|
||||
------------------------
|
||||
@@ -18,7 +15,7 @@ with customisations:
|
||||
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
|
||||
- You can tick the check-box next to every listed 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.
|
||||
@@ -27,7 +24,7 @@ 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].
|
||||
into the [game library].
|
||||
icarus displays a progress dialog during the import process,
|
||||
and a result window if any errors occurred.
|
||||
|
||||
@@ -47,12 +44,9 @@ The icarus Settings dialog contains the following settings:
|
||||
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.
|
||||
- **Create Manifests** causes icarus to
|
||||
include a [manifest] file
|
||||
inside the [game folder] for each imported game.
|
||||
See [Ignoring manifests](../concepts/manifests.md#ignoring-manifests)
|
||||
for details.
|
||||
- **Use Database** causes icarus to use manifest information
|
||||
@@ -65,5 +59,42 @@ The icarus Settings dialog contains the following settings:
|
||||
higan uses icarus to generate a manifest when a game is loaded,
|
||||
not just at import-time.
|
||||
|
||||
[gamelib]: ../concepts/game-library.md
|
||||
Command line
|
||||
------------
|
||||
|
||||
icarus can be launched in any of the following ways:
|
||||
|
||||
> icarus
|
||||
>
|
||||
> icarus \-\-import *FILE*
|
||||
>
|
||||
> icarus \-\-manifest *GAME*
|
||||
|
||||
When run without arguments,
|
||||
icarus runs interactively
|
||||
as described under [Bulk importing ROM files](#bulk-importing-rom-files) above.
|
||||
|
||||
When run with the `--import` flag,
|
||||
`FILE` should be the path to a ROM file
|
||||
for one of the consoles higan supports,
|
||||
or a `.zip` file containing such a ROM file.
|
||||
icarus will import it into the [game library]
|
||||
just as it would if running interactively,
|
||||
and the full path to the ressulting game folder
|
||||
is printed to icarus' standard output.
|
||||
|
||||
If the game cannot be imported correctly
|
||||
due to missing firmware,
|
||||
icarus prints no output.
|
||||
|
||||
When run with the `--manifest` flag,
|
||||
`GAME` should be the path to a [game folder],
|
||||
such as a game previously imported into the [game library].
|
||||
icarus will examine the game,
|
||||
come up with a [manifest] describing the game's memory layout,
|
||||
and print it to standard output.
|
||||
|
||||
[game library]: ../concepts/game-library.md
|
||||
[movgamelib]: ../concepts/game-library.md#moving-the-game-library
|
||||
[game folder]: ../concepts/game-folders.md
|
||||
[manifest]: ../concepts/manifests.md
|
||||
|
@@ -29,8 +29,8 @@ 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
|
||||
[vol]: https://helmet.kafuka.org/byuubackup2/viewtopic.php@f=4&t=1235&start=140.html#p42482
|
||||
[va6]: https://helmet.kafuka.org/byuubackup2/viewtopic.php@f=4&t=1235&start=130.html#p42195
|
||||
|
||||
Playing Game Boy Color games in Game Boy mode
|
||||
---------------------------------------------
|
||||
@@ -128,19 +128,23 @@ 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)
|
||||
but some game cartridges (such as *Pokémon Pinball*)
|
||||
included a rumble motor within the cartridge itself.
|
||||
Such cartridges generally used the "MBC5" memory mapper chip.
|
||||
|
||||
Because higan does not currently support
|
||||
game-specific controller features,
|
||||
to experience the rumble effect in higan
|
||||
you'll need to configure the console:
|
||||
To experience the rumble effect in higan,
|
||||
you'll need to configure the MBC5 "controller"
|
||||
connected to the "cartridge" port:
|
||||
|
||||
- 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 ports,
|
||||
select "Cartridge"
|
||||
- In the list of controllers,
|
||||
select "MBC5"
|
||||
- In the list of inputs,
|
||||
double-click "Rumble"
|
||||
or select it and press Enter
|
||||
|
@@ -51,7 +51,7 @@ Load a game
|
||||
-----------
|
||||
|
||||
From
|
||||
[the Library menu](interface/higan.md#the-library-menu),
|
||||
[the Systems menu](interface/higan.md#the-systems-menu),
|
||||
choose "Load ROM File ..."
|
||||
to open [a filesystem browser](interface/common.md#the-filesystem-browser),
|
||||
and choose the game you want to play.
|
||||
@@ -63,7 +63,7 @@ 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,
|
||||
from the Systems menu,
|
||||
which will list all the games for that console
|
||||
in the Game Library.
|
||||
|
||||
@@ -91,7 +91,7 @@ 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
|
||||
This doesn't apply to hand-held consoles
|
||||
like the Game Boy and WonderSwan,
|
||||
since the "controller" is always connected.
|
||||
This *does* apply to the Famicom,
|
||||
|
@@ -3,8 +3,13 @@ Release checklist
|
||||
|
||||
1. Commit the new release
|
||||
2. Tag the commit
|
||||
3. `git push`
|
||||
4. `git push --tags`
|
||||
5. Go to [the docs admin][rtd] and enable builds for the new tag.
|
||||
|
||||
[rtd]: https://readthedocs.org/dashboard/higan/versions/
|
||||
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. Copy `target-bsnes/resource/resource.?pp` to the `target-libretro` folder.
|
||||
7. `git push` to make the new changes available.
|
||||
|
||||
[rtd]: https://readthedocs.org/projects/higan/builds/
|
||||
|
BIN
firmware/cx4.data.rom
Normal file
BIN
firmware/sgb1.boot.rom
Normal file
BIN
firmware/sgb2.boot.rom
Normal file
59
genius/GNUmakefile
Normal file
@@ -0,0 +1,59 @@
|
||||
name := genius
|
||||
build := stable
|
||||
flags += -I..
|
||||
|
||||
nall.path := ../nall
|
||||
include $(nall.path)/GNUmakefile
|
||||
|
||||
hiro.path := ../hiro
|
||||
hiro.resource := data/$(name).rc
|
||||
include $(hiro.path)/GNUmakefile
|
||||
|
||||
objects := obj/genius.o
|
||||
|
||||
obj/genius.o: genius.cpp
|
||||
|
||||
all: $(hiro.objects) $(objects)
|
||||
$(info Linking out/$(name) ...)
|
||||
+@$(compiler) -o out/$(name) $(hiro.objects) $(objects) $(hiro.options) $(options)
|
||||
ifeq ($(platform),macos)
|
||||
rm -rf out/$(name).app
|
||||
mkdir -p out/$(name).app/Contents/MacOS/
|
||||
mkdir -p out/$(name).app/Contents/Resources/
|
||||
mv out/$(name) out/$(name).app/Contents/MacOS/$(name)
|
||||
cp data/$(name).plist out/$(name).app/Contents/Info.plist
|
||||
sips -s format icns data/$(name).png --out out/$(name).app/Contents/Resources/$(name).icns
|
||||
endif
|
||||
|
||||
verbose: hiro.verbose nall.verbose all;
|
||||
|
||||
clean:
|
||||
ifeq ($(platform),macos)
|
||||
rm -rf out/$(name).app
|
||||
endif
|
||||
$(call delete,obj/*)
|
||||
$(call delete,out/*)
|
||||
|
||||
install: all
|
||||
ifeq ($(platform),macos)
|
||||
cp -R out/$(name).app /Applications/$(name).app
|
||||
else ifneq ($(filter $(platform),linux bsd),)
|
||||
mkdir -p $(prefix)/bin/
|
||||
mkdir -p $(prefix)/share/applications/
|
||||
mkdir -p $(prefix)/share/icons/
|
||||
mkdir -p $(prefix)/share/$(name)/
|
||||
cp out/$(name) $(prefix)/bin/$(name)
|
||||
cp data/$(name).desktop $(prefix)/share/applications/$(name).desktop
|
||||
cp data/$(name).png $(prefix)/share/icons/$(name).png
|
||||
endif
|
||||
|
||||
uninstall:
|
||||
ifeq ($(platform),macos)
|
||||
rm -rf /Applications/$(name).app
|
||||
else ifneq ($(filter $(platform),linux bsd),)
|
||||
rm -f $(prefix)/bin/$(name)
|
||||
rm -f $(prefix)/share/applications/$(name).desktop
|
||||
rm -f $(prefix)/share/icons/$(name).png
|
||||
endif
|
||||
|
||||
-include obj/*.d
|
14
genius/data/genius.Manifest
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="genius" 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>
|
8
genius/data/genius.desktop
Normal file
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Name=genius
|
||||
Comment=Emulator
|
||||
Exec=genius
|
||||
Icon=genius
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Game;Emulator;
|
BIN
genius/data/genius.ico
Normal file
After Width: | Height: | Size: 33 KiB |
18
genius/data/genius.plist
Normal file
@@ -0,0 +1,18 @@
|
||||
<?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.genius</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>genius</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>genius</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>genius.icns</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
BIN
genius/data/genius.png
Normal file
After Width: | Height: | Size: 14 KiB |
2
genius/data/genius.rc
Normal file
@@ -0,0 +1,2 @@
|
||||
1 24 "genius.Manifest"
|
||||
2 ICON DISCARDABLE "genius.ico"
|
80
genius/data/genius.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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="icarus.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="62.34093"
|
||||
inkscape:cy="560"
|
||||
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:#b8b8ff;fill-opacity:1" />
|
||||
<g
|
||||
aria-label="氷"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:260.07336426px;line-height:1.25;font-family:KaiTi;-inkscape-font-specification:KaiTi;letter-spacing:0px;word-spacing:0px;fill:#4050e0;fill-opacity:1;stroke:#4050e0;stroke-width:6;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="text818">
|
||||
<path
|
||||
style="fill:#4050e0;fill-opacity:1;stroke:#4050e0;stroke-width:6;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 130.80961,146.24049 q 5.07956,5.07956 14.22276,15.23868 14.22277,-13.20685 25.39779,-27.42962 12.19094,-15.23867 11.17503,-24.38187 0,-10.15912 10.15912,-4.06365 10.15911,6.09547 14.22276,12.19094 4.06364,5.07956 -2.03182,7.11138 -6.09547,1.01591 -22.35006,14.22276 -15.23867,12.19094 -32.50917,26.4137 13.20685,11.17503 23.36597,20.31823 11.17502,9.14321 24.38187,18.28641 14.22277,8.1273 27.42962,14.22276 14.22276,5.07956 21.33414,7.11139 7.11138,2.03182 -3.04773,5.07955 -9.14321,2.03183 -23.36597,3.04774 -14.22276,0 -21.33414,-2.03182 -6.09547,-3.04774 -11.17503,-8.1273 -4.06365,-5.07956 -24.38188,-27.42961 -19.30232,-23.36597 -31.49326,-39.62055 1.01591,41.65237 2.03182,64.00243 2.03183,22.35005 0,34.54099 -2.03182,12.19094 -8.12729,19.30232 -5.07956,7.11138 -8.12729,4.06365 -2.03182,-2.03183 -7.11138,-11.17503 -5.07956,-8.12729 -16.254587,-15.23867 -11.175027,-8.1273 1.015912,-5.07956 12.190935,2.03182 16.254585,2.03182 4.06365,-1.01591 6.09547,-7.11138 2.03182,-7.11138 2.03182,-46.73193 0,-40.63646 -1.01591,-77.20928 -1.01591,-37.58873 -6.09547,-45.716022 -5.07956,-9.143205 4.06365,-7.111382 10.15911,1.015912 16.25458,5.079558 7.11138,3.047735 4.06365,9.143205 -3.04774,5.079557 -4.06365,15.238673 -1.01591,10.159118 -1.01591,51.811488 z"
|
||||
id="path822" />
|
||||
<path
|
||||
style="fill:#4050e0;fill-opacity:1;stroke:#4050e0;stroke-width:6;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 88.141325,149.28823 q 5.079558,1.01591 14.222765,7.11138 9.1432,6.09547 4.06364,10.15911 -5.07955,4.06365 -12.190935,18.28641 -6.09547,14.22276 -14.222763,25.39779 -8.127292,10.15912 -19.30232,19.30232 -11.175027,8.12729 -21.334143,12.19094 -9.143204,3.04774 -16.254585,5.07956 -6.095469,1.01591 5.079558,-6.09547 11.175027,-7.11138 21.334143,-17.2705 11.175027,-10.15911 18.286408,-21.33414 8.127293,-12.19094 11.175028,-20.31823 3.047735,-9.14321 3.047735,-14.22276 1.015911,-5.07956 -3.047735,-5.07956 -4.063646,0 -15.238674,4.06364 -10.159116,4.06365 -15.238674,6.09547 -4.063646,2.03183 -13.20685,-3.04773 -8.127293,-6.09547 2.031823,-6.09547 11.175027,-1.01591 24.381878,-5.07956 14.222762,-5.07956 17.270497,-7.11138 4.063646,-3.04773 9.143204,-2.03182 z"
|
||||
id="path820" />
|
||||
<path
|
||||
style="fill:#4050e0;fill-opacity:1;stroke:#4050e0;stroke-width:6;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 63.759447,101.54038 q 9.143204,1.01591 18.286409,5.07956 9.143204,4.06365 11.175027,11.17503 2.031823,7.11138 -1.015912,11.17503 -3.047734,4.06364 -15.238673,-4.06365 -11.175028,-9.1432 -16.254586,-16.25459 -5.079557,-8.12729 3.047735,-7.11138 z"
|
||||
id="path815" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
629
genius/genius.cpp
Normal file
@@ -0,0 +1,629 @@
|
||||
#include <nall/nall.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <hiro/hiro.hpp>
|
||||
using namespace hiro;
|
||||
|
||||
#include "genius.hpp"
|
||||
unique_pointer<ListWindow> listWindow;
|
||||
unique_pointer<GameWindow> gameWindow;
|
||||
unique_pointer<MemoryWindow> memoryWindow;
|
||||
unique_pointer<OscillatorWindow> oscillatorWindow;
|
||||
|
||||
//
|
||||
|
||||
ListWindow::ListWindow() {
|
||||
listWindow = this;
|
||||
|
||||
fileMenu.setText("File");
|
||||
newAction.setText("New").onActivate([&] { newDatabase(); });
|
||||
openAction.setText("Open ...").onActivate([&] {
|
||||
if(auto location = BrowserDialog().setParent(*this).setFilters({"*.bml"}).openFile()) {
|
||||
loadDatabase(location);
|
||||
}
|
||||
});
|
||||
saveAction.setText("Save").onActivate([&] {
|
||||
if(!location) return saveAsAction.doActivate();
|
||||
saveDatabase(location);
|
||||
});
|
||||
saveAsAction.setText("Save As ...").onActivate([&] {
|
||||
if(auto location = BrowserDialog().setParent(*this).setFilters({"*.bml"}).saveFile()) {
|
||||
saveDatabase(location);
|
||||
}
|
||||
});
|
||||
quitAction.setText("Quit").onActivate([&] { quit(); });
|
||||
|
||||
helpMenu.setText("Help");
|
||||
aboutAction.setText("About ...").onActivate([&] {
|
||||
MessageDialog().setParent(*this).setTitle("About").setText({
|
||||
"genius\n",
|
||||
"Author: byuu\n",
|
||||
"Website: https://byuu.org/"
|
||||
}).information();
|
||||
});
|
||||
|
||||
layout.setPadding(5);
|
||||
gameList.setHeadered();
|
||||
gameList.onActivate([&] { modifyButton.doActivate(); });
|
||||
gameList.onChange([&] { updateWindow(); });
|
||||
appendButton.setText("Append").onActivate([&] {
|
||||
setEnabled(false);
|
||||
gameWindow->show();
|
||||
});
|
||||
modifyButton.setText("Modify").onActivate([&] {
|
||||
if(auto item = gameList.selected()) {
|
||||
setEnabled(false);
|
||||
gameWindow->show(games[item.offset()]);
|
||||
}
|
||||
});
|
||||
removeButton.setText("Remove").onActivate([&] { removeGame(); });
|
||||
|
||||
onClose([&] { quit(); });
|
||||
|
||||
setSize({820, 600});
|
||||
reloadList();
|
||||
updateWindow();
|
||||
setCentered();
|
||||
}
|
||||
|
||||
auto ListWindow::quit() -> void {
|
||||
if(!modified || MessageDialog().setParent(*this).setText({
|
||||
"Are you sure you want to quit without saving your changes?"
|
||||
}).question() == "Yes") {
|
||||
Application::quit();
|
||||
}
|
||||
}
|
||||
|
||||
auto ListWindow::reloadList() -> void {
|
||||
gameList.reset();
|
||||
gameList.append(TableViewColumn().setText("Name").setExpandable());
|
||||
gameList.append(TableViewColumn().setText("Region"));
|
||||
gameList.append(TableViewColumn().setText("Revision"));
|
||||
gameList.append(TableViewColumn().setText("Board"));
|
||||
for(auto& game : games) {
|
||||
TableViewItem item{&gameList};
|
||||
item.append(TableViewCell().setText(game.name));
|
||||
item.append(TableViewCell().setText(game.region));
|
||||
item.append(TableViewCell().setText(game.revision));
|
||||
item.append(TableViewCell().setText(game.board));
|
||||
}
|
||||
Application::processEvents();
|
||||
gameList.resizeColumns();
|
||||
}
|
||||
|
||||
auto ListWindow::updateWindow() -> void {
|
||||
modifyButton.setEnabled((bool)gameList.selected());
|
||||
removeButton.setEnabled((bool)gameList.selected());
|
||||
string name = Location::base(location);
|
||||
if(!name) name = "(Untitled)";
|
||||
setTitle({modified ? "*" : "", name, " [", games.size(), "] - genius"});
|
||||
}
|
||||
|
||||
auto ListWindow::newDatabase() -> void {
|
||||
games.reset();
|
||||
modified = false;
|
||||
location = "";
|
||||
reloadList();
|
||||
updateWindow();
|
||||
}
|
||||
|
||||
auto ListWindow::loadDatabase(string location) -> void {
|
||||
auto document = BML::unserialize(string::read(location));
|
||||
|
||||
games.reset();
|
||||
for(auto node : document.find("game")) {
|
||||
Game game;
|
||||
game.sha256 = node["sha256"].text();
|
||||
game.label = node["label"].text();
|
||||
game.name = node["name"].text();
|
||||
game.region = node["region"].text();
|
||||
game.revision = node["revision"].text();
|
||||
game.board = node["board"].text();
|
||||
for(auto object : node["board"]) {
|
||||
Component component;
|
||||
if(object.name() == "memory") {
|
||||
component.type = Component::Type::Memory;
|
||||
component.memory.type = object["type"].text();
|
||||
component.memory.size = object["size"].text();
|
||||
component.memory.content = object["content"].text();
|
||||
component.memory.manufacturer = object["manufacturer"].text();
|
||||
component.memory.architecture = object["architecture"].text();
|
||||
component.memory.identifier = object["identifier"].text();
|
||||
component.memory.Volatile = (bool)object["volatile"];
|
||||
}
|
||||
if(object.name() == "oscillator") {
|
||||
component.type = Component::Type::Oscillator;
|
||||
component.oscillator.frequency = object["frequency"].text();
|
||||
}
|
||||
game.components.append(component);
|
||||
}
|
||||
game.note = node["note"].text();
|
||||
games.append(game);
|
||||
}
|
||||
|
||||
modified = false;
|
||||
this->location = location;
|
||||
reloadList();
|
||||
updateWindow();
|
||||
}
|
||||
|
||||
auto ListWindow::saveDatabase(string location) -> void {
|
||||
auto fp = file::open(location, file::mode::write);
|
||||
if(!fp) return MessageDialog().setParent(*this).setText({
|
||||
"Error: failed to write file.\n\n",
|
||||
"Name: ", location
|
||||
}).error(), void();
|
||||
|
||||
auto copy = games;
|
||||
copy.sort([](auto x, auto y) {
|
||||
return string::icompare(
|
||||
{x.name, "\n", x.region, "\n", x.revision},
|
||||
{y.name, "\n", y.region, "\n", y.revision}
|
||||
) < 0;
|
||||
});
|
||||
|
||||
fp.print("database\n");
|
||||
fp.print(" revision: ", chrono::local::date(), "\n\n");
|
||||
|
||||
for(auto& game : copy) {
|
||||
fp.print("game\n");
|
||||
fp.print(" sha256: ", game.sha256, "\n");
|
||||
if(game.label)
|
||||
fp.print(" label: ", game.label, "\n");
|
||||
fp.print(" name: ", game.name, "\n");
|
||||
fp.print(" region: ", game.region, "\n");
|
||||
fp.print(" revision: ", game.revision, "\n");
|
||||
if(game.board)
|
||||
fp.print(" board: ", game.board, "\n");
|
||||
else if(game.components)
|
||||
fp.print(" board\n");
|
||||
for(auto& component : game.components) {
|
||||
if(component.type == Component::Type::Memory) {
|
||||
fp.print(" memory\n");
|
||||
fp.print(" type: ", component.memory.type, "\n");
|
||||
fp.print(" size: ", component.memory.size, "\n");
|
||||
fp.print(" content: ", component.memory.content, "\n");
|
||||
if(component.memory.manufacturer)
|
||||
fp.print(" manufacturer: ", component.memory.manufacturer, "\n");
|
||||
if(component.memory.architecture)
|
||||
fp.print(" architecture: ", component.memory.architecture, "\n");
|
||||
if(component.memory.identifier)
|
||||
fp.print(" identifier: ", component.memory.identifier, "\n");
|
||||
if(component.memory.Volatile)
|
||||
fp.print(" volatile\n");
|
||||
}
|
||||
|
||||
if(component.type == Component::Type::Oscillator) {
|
||||
fp.print(" oscillator\n");
|
||||
fp.print(" frequency: ", component.oscillator.frequency, "\n");
|
||||
}
|
||||
}
|
||||
if(game.note)
|
||||
fp.print(" note: ", game.note, "\n");
|
||||
fp.print("\n");
|
||||
}
|
||||
|
||||
modified = false;
|
||||
this->location = location;
|
||||
updateWindow();
|
||||
}
|
||||
|
||||
auto ListWindow::appendGame(Game game) -> void {
|
||||
modified = true;
|
||||
auto offset = games.size();
|
||||
games.append(game);
|
||||
reloadList();
|
||||
gameList.item(offset).setSelected().setFocused();
|
||||
updateWindow();
|
||||
}
|
||||
|
||||
auto ListWindow::modifyGame(Game game) -> void {
|
||||
if(auto item = gameList.selected()) {
|
||||
modified = true;
|
||||
auto offset = item.offset();
|
||||
games[offset] = game;
|
||||
reloadList();
|
||||
gameList.item(offset).setSelected().setFocused();
|
||||
updateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
auto ListWindow::removeGame() -> void {
|
||||
if(auto item = gameList.selected()) {
|
||||
if(MessageDialog().setParent(*this).setText({
|
||||
"Are you sure you want to permanently remove this game?\n\n",
|
||||
"Name: ", item.cell(0).text()
|
||||
}).question() == "Yes") {
|
||||
modified = true;
|
||||
games.remove(item.offset());
|
||||
reloadList();
|
||||
updateWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
GameWindow::GameWindow() {
|
||||
gameWindow = this;
|
||||
|
||||
layout.setPadding(5);
|
||||
hashLabel.setText("SHA256:").setAlignment(1.0);
|
||||
hashEdit.setFont(Font().setFamily(Font::Mono)).onChange([&] { modified = true, updateWindow(); });
|
||||
regionLabel.setText("Region:").setAlignment(1.0);
|
||||
regionEdit.setFont(Font().setFamily(Font::Mono)).onChange([&] { modified = true, updateWindow(); });
|
||||
revisionLabel.setText("Revision:");
|
||||
revisionEdit.setFont(Font().setFamily(Font::Mono)).onChange([&] { modified = true, updateWindow(); });
|
||||
boardLabel.setText("Board:");
|
||||
boardEdit.setFont(Font().setFamily(Font::Mono)).onChange([&] { modified = true, updateWindow(); });
|
||||
nameLabel.setText("Name:").setAlignment(1.0);
|
||||
nameEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
labelLabel.setText("Label:").setAlignment(1.0);
|
||||
labelEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
noteLabel.setText("Note:").setAlignment(1.0);
|
||||
noteEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
componentLabel.setText("Tree:").setAlignment({1.0, 0.0});
|
||||
componentTree.onActivate([&] { modifyComponentButton.doActivate(); });
|
||||
componentTree.onChange([&] { updateWindow(); });
|
||||
appendMemoryButton.setText("Memory").onActivate([&] {
|
||||
setEnabled(false);
|
||||
memoryWindow->show();
|
||||
});
|
||||
appendOscillatorButton.setText("Oscillator").onActivate([&] {
|
||||
setEnabled(false);
|
||||
oscillatorWindow->show();
|
||||
});
|
||||
modifyComponentButton.setText("Modify").onActivate([&] {
|
||||
if(auto item = componentTree.selected()) {
|
||||
setEnabled(false);
|
||||
auto path = item.path().split("/");
|
||||
auto offset = path(0).natural();
|
||||
Component component = game.components[offset];
|
||||
if(component.type == Component::Type::Memory) {
|
||||
memoryWindow->show(component.memory);
|
||||
}
|
||||
if(component.type == Component::Type::Oscillator) {
|
||||
oscillatorWindow->show(component.oscillator);
|
||||
}
|
||||
}
|
||||
});
|
||||
removeComponentButton.setText("Remove").onActivate([&] { removeComponent(); });
|
||||
acceptButton.setText("Accept").onActivate([&] { accept(); });
|
||||
cancelButton.setText("Cancel").onActivate([&] { cancel(); });
|
||||
|
||||
onClose([&] { cancel(); });
|
||||
|
||||
setSize({640, 480});
|
||||
setDismissable();
|
||||
}
|
||||
|
||||
auto GameWindow::show(Game game) -> void {
|
||||
this->game = game;
|
||||
modified = false;
|
||||
create = !game.sha256;
|
||||
|
||||
hashEdit.setText(game.sha256);
|
||||
regionEdit.setText(game.region);
|
||||
revisionEdit.setText(game.revision);
|
||||
boardEdit.setText(game.board);
|
||||
nameEdit.setText(game.name);
|
||||
labelEdit.setText(game.label);
|
||||
noteEdit.setText(game.note);
|
||||
acceptButton.setText(create ? "Create" : "Apply");
|
||||
|
||||
reloadList();
|
||||
updateWindow();
|
||||
setCentered(*listWindow);
|
||||
setVisible();
|
||||
|
||||
if(create) {
|
||||
hashEdit.setFocused();
|
||||
} else {
|
||||
cancelButton.setFocused();
|
||||
}
|
||||
}
|
||||
|
||||
auto GameWindow::accept() -> void {
|
||||
game.sha256 = hashEdit.text().strip();
|
||||
game.region = regionEdit.text().strip();
|
||||
game.revision = revisionEdit.text().strip();
|
||||
game.board = boardEdit.text().strip();
|
||||
game.name = nameEdit.text().strip();
|
||||
game.label = labelEdit.text().strip();
|
||||
game.note = noteEdit.text().strip();
|
||||
|
||||
if(create) {
|
||||
listWindow->appendGame(game);
|
||||
} else {
|
||||
listWindow->modifyGame(game);
|
||||
}
|
||||
|
||||
memoryWindow->setVisible(false);
|
||||
setVisible(false);
|
||||
listWindow->setEnabled();
|
||||
listWindow->setFocused();
|
||||
}
|
||||
|
||||
auto GameWindow::cancel() -> void {
|
||||
if(!modified || MessageDialog().setParent(*this).setText({
|
||||
"Are you sure you want to discard your changes to this game?"
|
||||
}).question() == "Yes") {
|
||||
memoryWindow->setVisible(false);
|
||||
setVisible(false);
|
||||
listWindow->setEnabled();
|
||||
listWindow->setFocused();
|
||||
}
|
||||
}
|
||||
|
||||
auto GameWindow::reloadList() -> void {
|
||||
componentTree.reset();
|
||||
uint counter = 1;
|
||||
for(auto& component : game.components) {
|
||||
TreeViewItem item;
|
||||
|
||||
string index = {"[", counter++, "] "};
|
||||
if(component.type == Component::Type::Memory) {
|
||||
item.setText({index, "Memory"});
|
||||
item.append(TreeViewItem().setText({"Type: ", component.memory.type}));
|
||||
item.append(TreeViewItem().setText({"Size: ", component.memory.size}));
|
||||
item.append(TreeViewItem().setText({"Content: ", component.memory.content}));
|
||||
if(component.memory.manufacturer)
|
||||
item.append(TreeViewItem().setText({"Manufacturer: ", component.memory.manufacturer}));
|
||||
if(component.memory.architecture)
|
||||
item.append(TreeViewItem().setText({"Architecture: ", component.memory.architecture}));
|
||||
if(component.memory.identifier)
|
||||
item.append(TreeViewItem().setText({"Identifier: ", component.memory.identifier}));
|
||||
if(component.memory.Volatile)
|
||||
item.append(TreeViewItem().setText({"Volatile"}));
|
||||
}
|
||||
|
||||
if(component.type == Component::Type::Oscillator) {
|
||||
item.setText({index, "Oscillator"});
|
||||
item.append(TreeViewItem().setText({"Frequency: ", component.oscillator.frequency}));
|
||||
}
|
||||
|
||||
componentTree.append(item);
|
||||
}
|
||||
|
||||
Application::processEvents();
|
||||
for(auto& item : componentTree.items()) item.setExpanded();
|
||||
}
|
||||
|
||||
auto GameWindow::updateWindow() -> void {
|
||||
bool valid = true;
|
||||
bool hashValid = hashEdit.text().strip().size() == 64;
|
||||
hashEdit.setEditable(!hashValid).setBackgroundColor(
|
||||
!create || hashValid ? Color{192, 255, 192}
|
||||
: (valid = false, Color{255, 224, 224}));
|
||||
regionEdit.setBackgroundColor(regionEdit.text().strip() ? Color{} : (valid = false, Color{255, 224, 224}));
|
||||
revisionEdit.setBackgroundColor(revisionEdit.text().strip() ? Color{} : (valid = false, Color{255, 224, 224}));
|
||||
boardEdit.setBackgroundColor(boardEdit.text().strip() ? Color{} : (Color{255, 255, 240}));
|
||||
nameEdit.setBackgroundColor(nameEdit.text().strip() ? Color{} : (valid = false, Color{255, 224, 224}));
|
||||
labelEdit.setBackgroundColor(labelEdit.text().strip() ? Color{} : (Color{255, 255, 240}));
|
||||
noteEdit.setBackgroundColor(noteEdit.text().strip() ? Color{} : (Color{255, 255, 240}));
|
||||
modifyComponentButton.setEnabled((bool)componentTree.selected());
|
||||
removeComponentButton.setEnabled((bool)componentTree.selected());
|
||||
acceptButton.setEnabled(valid);
|
||||
setTitle({modified ? "*" : "", create ? "Add New Game" : "Modify Game Details"});
|
||||
if(create && hashValid && hashEdit.focused()) regionEdit.setFocused();
|
||||
}
|
||||
|
||||
auto GameWindow::appendComponent(Component component) -> void {
|
||||
modified = true;
|
||||
auto offset = game.components.size();
|
||||
game.components.append(component);
|
||||
reloadList();
|
||||
componentTree.item(offset).setSelected().setFocused();
|
||||
updateWindow();
|
||||
}
|
||||
|
||||
auto GameWindow::modifyComponent(Component component) -> void {
|
||||
if(auto item = componentTree.selected()) {
|
||||
modified = true;
|
||||
auto path = item.path().split("/");
|
||||
auto offset = path(0).natural();
|
||||
game.components[offset] = component;
|
||||
reloadList();
|
||||
componentTree.item(offset).setSelected().setFocused();
|
||||
updateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
auto GameWindow::removeComponent() -> void {
|
||||
if(auto item = componentTree.selected()) {
|
||||
if(MessageDialog().setParent(*this).setText({
|
||||
"Are you sure you want to permanently remove this component?"
|
||||
}).question() == "Yes") {
|
||||
modified = true;
|
||||
auto path = item.path().split("/");
|
||||
auto offset = path(0).natural();
|
||||
game.components.remove(offset);
|
||||
reloadList();
|
||||
updateWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
MemoryWindow::MemoryWindow() {
|
||||
memoryWindow = this;
|
||||
|
||||
layout.setPadding(5);
|
||||
typeLabel.setText("Type:").setAlignment(1.0);
|
||||
typeEdit.append(ComboEditItem().setText("ROM"));
|
||||
typeEdit.append(ComboEditItem().setText("EEPROM"));
|
||||
typeEdit.append(ComboEditItem().setText("Flash"));
|
||||
typeEdit.append(ComboEditItem().setText("RAM"));
|
||||
typeEdit.append(ComboEditItem().setText("RTC"));
|
||||
typeEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
sizeLabel.setText("Size:").setAlignment(1.0);
|
||||
sizeEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
contentLabel.setText("Content:").setAlignment(1.0);
|
||||
contentEdit.append(ComboEditItem().setText("Program"));
|
||||
contentEdit.append(ComboEditItem().setText("Data"));
|
||||
contentEdit.append(ComboEditItem().setText("Character"));
|
||||
contentEdit.append(ComboEditItem().setText("Save"));
|
||||
contentEdit.append(ComboEditItem().setText("Time"));
|
||||
contentEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
manufacturerLabel.setText("Manufacturer:").setAlignment(1.0);
|
||||
manufacturerEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
architectureLabel.setText("Architecture:").setAlignment(1.0);
|
||||
architectureEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
identifierLabel.setText("Identifier:").setAlignment(1.0);
|
||||
identifierEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
volatileOption.setText("Volatile").onToggle([&] { modified = true, updateWindow(); });
|
||||
acceptButton.setText("Accept").onActivate([&] { accept(); });
|
||||
cancelButton.setText("Cancel").onActivate([&] { cancel(); });
|
||||
|
||||
onClose([&] { cancel(); });
|
||||
|
||||
setSize({320, layout.minimumSize().height()});
|
||||
setDismissable();
|
||||
}
|
||||
|
||||
auto MemoryWindow::show(Memory memory) -> void {
|
||||
this->memory = memory;
|
||||
modified = false;
|
||||
create = !memory.type;
|
||||
|
||||
typeEdit.setText(memory.type);
|
||||
sizeEdit.setText(memory.size);
|
||||
contentEdit.setText(memory.content);
|
||||
manufacturerEdit.setText(memory.manufacturer);
|
||||
architectureEdit.setText(memory.architecture);
|
||||
identifierEdit.setText(memory.identifier);
|
||||
volatileOption.setChecked(memory.Volatile);
|
||||
|
||||
updateWindow();
|
||||
setCentered(*gameWindow);
|
||||
setVisible();
|
||||
|
||||
typeEdit.setFocused();
|
||||
}
|
||||
|
||||
auto MemoryWindow::accept() -> void {
|
||||
memory.type = typeEdit.text().strip();
|
||||
memory.size = sizeEdit.text().strip();
|
||||
memory.content = contentEdit.text().strip();
|
||||
memory.manufacturer = manufacturerEdit.text().strip();
|
||||
memory.architecture = architectureEdit.text().strip();
|
||||
memory.identifier = identifierEdit.text().strip();
|
||||
memory.Volatile = volatileOption.checked() && (memory.type == "RAM" || memory.type == "RTC");
|
||||
|
||||
Component component{Component::Type::Memory};
|
||||
component.memory = memory;
|
||||
if(create) {
|
||||
gameWindow->appendComponent(component);
|
||||
} else {
|
||||
gameWindow->modifyComponent(component);
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
gameWindow->setEnabled();
|
||||
gameWindow->setFocused();
|
||||
}
|
||||
|
||||
auto MemoryWindow::cancel() -> void {
|
||||
if(!modified || MessageDialog().setParent(*this).setText({
|
||||
"Are you sure you want to discard your changes to this memory?"
|
||||
}).question() == "Yes") {
|
||||
setVisible(false);
|
||||
gameWindow->setEnabled();
|
||||
gameWindow->setFocused();
|
||||
}
|
||||
}
|
||||
|
||||
auto MemoryWindow::updateWindow() -> void {
|
||||
bool valid = true;
|
||||
typeEdit.setBackgroundColor(typeEdit.text().strip() ? Color{} : (valid = false, Color{255, 224, 224}));
|
||||
sizeEdit.setBackgroundColor(sizeEdit.text().strip() ? Color{} : (valid = false, Color{255, 224, 224}));
|
||||
contentEdit.setBackgroundColor(contentEdit.text().strip() ? Color{} : (valid = false, Color{255, 224, 224}));
|
||||
manufacturerEdit.setBackgroundColor(manufacturerEdit.text().strip() ? Color{} : (Color{255, 255, 240}));
|
||||
architectureEdit.setBackgroundColor(architectureEdit.text().strip() ? Color{} : (Color{255, 255, 240}));
|
||||
identifierEdit.setBackgroundColor(identifierEdit.text().strip() ? Color{} : (Color{255, 255, 240}));
|
||||
volatileOption.setEnabled(typeEdit.text().strip() == "RAM" || typeEdit.text().strip() == "RTC");
|
||||
acceptButton.setEnabled(valid);
|
||||
setTitle({modified ? "*" : "", create ? "Add New Memory" : "Modify Memory Details"});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
OscillatorWindow::OscillatorWindow() {
|
||||
oscillatorWindow = this;
|
||||
|
||||
layout.setPadding(5);
|
||||
frequencyLabel.setText("Frequency:").setAlignment(1.0);
|
||||
frequencyEdit.onChange([&] { modified = true, updateWindow(); });
|
||||
acceptButton.setText("Accept").onActivate([&] { accept(); });
|
||||
cancelButton.setText("Cancel").onActivate([&] { cancel(); });
|
||||
|
||||
onClose([&] { cancel(); });
|
||||
|
||||
setSize({320, layout.minimumSize().height()});
|
||||
setDismissable();
|
||||
}
|
||||
|
||||
auto OscillatorWindow::show(Oscillator oscillator) -> void {
|
||||
this->oscillator = oscillator;
|
||||
modified = false;
|
||||
create = !oscillator.frequency;
|
||||
|
||||
frequencyEdit.setText(oscillator.frequency);
|
||||
|
||||
updateWindow();
|
||||
setCentered(*gameWindow);
|
||||
setVisible();
|
||||
|
||||
frequencyEdit.setFocused();
|
||||
}
|
||||
|
||||
auto OscillatorWindow::accept() -> void {
|
||||
oscillator.frequency = frequencyEdit.text().strip();
|
||||
|
||||
Component component{Component::Type::Oscillator};
|
||||
component.oscillator = oscillator;
|
||||
if(create) {
|
||||
gameWindow->appendComponent(component);
|
||||
} else {
|
||||
gameWindow->modifyComponent(component);
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
gameWindow->setEnabled();
|
||||
gameWindow->setFocused();
|
||||
}
|
||||
|
||||
auto OscillatorWindow::cancel() -> void {
|
||||
if(!modified || MessageDialog().setParent(*this).setText({
|
||||
"Are you sure you want to discard your changes to this property?"
|
||||
}).question() == "Yes") {
|
||||
setVisible(false);
|
||||
gameWindow->setEnabled();
|
||||
gameWindow->setFocused();
|
||||
}
|
||||
}
|
||||
|
||||
auto OscillatorWindow::updateWindow() -> void {
|
||||
bool valid = true;
|
||||
frequencyEdit.setBackgroundColor(frequencyEdit.text().strip() ? Color{} : (valid = false, Color{255, 224, 224}));
|
||||
acceptButton.setEnabled(valid);
|
||||
setTitle({modified ? "*" : "", create ? "Add New Property" : "Modify Property Details"});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto hiro::initialize() -> void {
|
||||
Application::setName("genius");
|
||||
}
|
||||
|
||||
#include <nall/main.hpp>
|
||||
auto nall::main(Arguments) -> void {
|
||||
new ListWindow;
|
||||
new GameWindow;
|
||||
new MemoryWindow;
|
||||
new OscillatorWindow;
|
||||
|
||||
listWindow->setVisible();
|
||||
Application::run();
|
||||
}
|
178
genius/genius.hpp
Normal file
@@ -0,0 +1,178 @@
|
||||
struct Memory {
|
||||
string type;
|
||||
string size;
|
||||
string content;
|
||||
string manufacturer;
|
||||
string architecture;
|
||||
string identifier;
|
||||
boolean Volatile;
|
||||
};
|
||||
|
||||
struct Oscillator {
|
||||
string frequency;
|
||||
};
|
||||
|
||||
//variant meta-class
|
||||
struct Component {
|
||||
enum class Type : uint {
|
||||
Memory,
|
||||
Oscillator,
|
||||
} type;
|
||||
Memory memory;
|
||||
Oscillator oscillator;
|
||||
};
|
||||
|
||||
struct Game {
|
||||
string sha256;
|
||||
string region;
|
||||
string revision;
|
||||
string board;
|
||||
string name;
|
||||
string label;
|
||||
string note;
|
||||
vector<Component> components;
|
||||
};
|
||||
|
||||
struct ListWindow : Window {
|
||||
ListWindow();
|
||||
auto quit() -> void;
|
||||
auto reloadList() -> void;
|
||||
auto updateWindow() -> void;
|
||||
auto newDatabase() -> void;
|
||||
auto loadDatabase(string) -> void;
|
||||
auto saveDatabase(string) -> void;
|
||||
auto appendGame(Game) -> void;
|
||||
auto modifyGame(Game) -> void;
|
||||
auto removeGame() -> void;
|
||||
|
||||
private:
|
||||
bool modified = false;
|
||||
vector<Game> games;
|
||||
string location;
|
||||
|
||||
MenuBar menuBar{this};
|
||||
Menu fileMenu{&menuBar};
|
||||
MenuItem newAction{&fileMenu};
|
||||
MenuItem openAction{&fileMenu};
|
||||
MenuItem saveAction{&fileMenu};
|
||||
MenuItem saveAsAction{&fileMenu};
|
||||
MenuSeparator quitSeparator{&fileMenu};
|
||||
MenuItem quitAction{&fileMenu};
|
||||
Menu helpMenu{&menuBar};
|
||||
MenuItem aboutAction{&helpMenu};
|
||||
|
||||
HorizontalLayout layout{this};
|
||||
TableView gameList{&layout, Size{~0, ~0}};
|
||||
VerticalLayout controlLayout{&layout, Size{80, ~0}};
|
||||
Button appendButton{&controlLayout, Size{~0, 0}};
|
||||
Button modifyButton{&controlLayout, Size{~0, 0}};
|
||||
Button removeButton{&controlLayout, Size{~0, 0}};
|
||||
};
|
||||
|
||||
struct GameWindow : Window {
|
||||
GameWindow();
|
||||
auto show(Game = {}) -> void;
|
||||
auto accept() -> void;
|
||||
auto cancel() -> void;
|
||||
auto reloadList() -> void;
|
||||
auto updateWindow() -> void;
|
||||
auto appendComponent(Component) -> void;
|
||||
auto modifyComponent(Component) -> void;
|
||||
auto removeComponent() -> void;
|
||||
|
||||
private:
|
||||
bool modified = false;
|
||||
bool create = true;
|
||||
Game game;
|
||||
|
||||
VerticalLayout layout{this};
|
||||
HorizontalLayout hashLayout{&layout, Size{~0, 0}};
|
||||
Label hashLabel{&hashLayout, Size{50, 0}};
|
||||
LineEdit hashEdit{&hashLayout, Size{~0, 0}};
|
||||
HorizontalLayout infoLayout{&layout, Size{~0, 0}};
|
||||
Label regionLabel{&infoLayout, Size{50, 0}};
|
||||
LineEdit regionEdit{&infoLayout, Size{~0, 0}};
|
||||
Label revisionLabel{&infoLayout, Size{0, 0}};
|
||||
LineEdit revisionEdit{&infoLayout, Size{~0, 0}};
|
||||
Label boardLabel{&infoLayout, Size{0, 0}};
|
||||
LineEdit boardEdit{&infoLayout, Size{~0, 0}, 0};
|
||||
HorizontalLayout nameLayout{&layout, Size{~0, 0}};
|
||||
Label nameLabel{&nameLayout, Size{50, 0}};
|
||||
LineEdit nameEdit{&nameLayout, Size{~0, 0}};
|
||||
HorizontalLayout labelLayout{&layout, Size{~0, 0}};
|
||||
Label labelLabel{&labelLayout, Size{50, 0}};
|
||||
LineEdit labelEdit{&labelLayout, Size{~0, 0}};
|
||||
HorizontalLayout noteLayout{&layout, Size{~0, 0}};
|
||||
Label noteLabel{¬eLayout, Size{50, 0}};
|
||||
LineEdit noteEdit{¬eLayout, Size{~0, 0}};
|
||||
HorizontalLayout lowerLayout{&layout, Size{~0, ~0}};
|
||||
Label componentLabel{&lowerLayout, Size{50, ~0}};
|
||||
TreeView componentTree{&lowerLayout, Size{~0, ~0}};
|
||||
VerticalLayout controlLayout{&lowerLayout, Size{0, ~0}};
|
||||
Button appendMemoryButton{&controlLayout, Size{80, 0}};
|
||||
Button appendOscillatorButton{&controlLayout, Size{80, 0}};
|
||||
Button modifyComponentButton{&controlLayout, Size{80, 0}};
|
||||
Button removeComponentButton{&controlLayout, Size{80, 0}};
|
||||
Widget controlSpacer{&controlLayout, Size{0, ~0}};
|
||||
Button acceptButton{&controlLayout, Size{80, 0}};
|
||||
Button cancelButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
|
||||
struct MemoryWindow : Window {
|
||||
MemoryWindow();
|
||||
auto show(Memory = {}) -> void;
|
||||
auto accept() -> void;
|
||||
auto cancel() -> void;
|
||||
auto updateWindow() -> void;
|
||||
|
||||
private:
|
||||
bool modified = false;
|
||||
bool create = true;
|
||||
Memory memory;
|
||||
|
||||
VerticalLayout layout{this};
|
||||
HorizontalLayout infoLayout{&layout, Size{~0, 0}};
|
||||
Label typeLabel{&infoLayout, Size{80, 0}};
|
||||
ComboEdit typeEdit{&infoLayout, Size{~0, 0}};
|
||||
Label sizeLabel{&infoLayout, Size{0, 0}};
|
||||
LineEdit sizeEdit{&infoLayout, Size{~0, 0}};
|
||||
HorizontalLayout contentLayout{&layout, Size{~0, 0}};
|
||||
Label contentLabel{&contentLayout, Size{80, 0}};
|
||||
ComboEdit contentEdit{&contentLayout, Size{~0, 0}};
|
||||
HorizontalLayout manufacturerLayout{&layout, Size{~0, 0}};
|
||||
Label manufacturerLabel{&manufacturerLayout, Size{80, 0}};
|
||||
LineEdit manufacturerEdit{&manufacturerLayout, Size{~0, 0}};
|
||||
HorizontalLayout architectureLayout{&layout, Size{~0, 0}};
|
||||
Label architectureLabel{&architectureLayout, Size{80, 0}};
|
||||
LineEdit architectureEdit{&architectureLayout, Size{~0, 0}};
|
||||
HorizontalLayout identifierLayout{&layout, Size{~0, 0}};
|
||||
Label identifierLabel{&identifierLayout, Size{80, 0}};
|
||||
LineEdit identifierEdit{&identifierLayout, Size{~0, 0}};
|
||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||
Widget controlSpacer{&controlLayout, Size{~0, 0}};
|
||||
CheckLabel volatileOption{&controlLayout, Size{0, 0}};
|
||||
Button acceptButton{&controlLayout, Size{80, 0}};
|
||||
Button cancelButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
|
||||
struct OscillatorWindow : Window {
|
||||
OscillatorWindow();
|
||||
auto show(Oscillator = {}) -> void;
|
||||
auto accept() -> void;
|
||||
auto cancel() -> void;
|
||||
auto updateWindow() -> void;
|
||||
|
||||
private:
|
||||
bool modified = false;
|
||||
bool create = true;
|
||||
Oscillator oscillator;
|
||||
|
||||
VerticalLayout layout{this};
|
||||
HorizontalLayout frequencyLayout{&layout, Size{~0, 0}};
|
||||
Label frequencyLabel{&frequencyLayout, Size{60, 0}};
|
||||
LineEdit frequencyEdit{&frequencyLayout, Size{~0, 0}};
|
||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||
Widget controlSpacer{&controlLayout, Size{~0, 0}};
|
||||
Button acceptButton{&controlLayout, Size{80, 0}};
|
||||
Button cancelButton{&controlLayout, Size{80, 0}};
|
||||
};
|
2
genius/obj/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.o
|
||||
*.d
|
1
genius/out/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
genius
|
@@ -1,16 +1,15 @@
|
||||
build := optimize
|
||||
include ../nall/GNUmakefile
|
||||
|
||||
target := bsnes
|
||||
binary := application
|
||||
target := tomoko
|
||||
objects := libco emulator audio video resource
|
||||
|
||||
build := performance
|
||||
openmp := true
|
||||
flags += -I. -I..
|
||||
|
||||
nall.path := ../nall
|
||||
include $(nall.path)/GNUmakefile
|
||||
|
||||
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 += -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
link += -Wl,-enable-auto-import
|
||||
link += -Wl,-enable-runtime-pseudo-reloc
|
||||
else ifeq ($(binary),library)
|
||||
@@ -23,8 +22,6 @@ else ifeq ($(platform),macos)
|
||||
link += -dynamiclib
|
||||
endif
|
||||
else ifneq ($(filter $(platform),linux bsd),)
|
||||
flags += -fopenmp
|
||||
link += -fopenmp
|
||||
ifeq ($(binary),application)
|
||||
flags += -march=native
|
||||
link += -Wl,-export-dynamic
|
||||
@@ -37,29 +34,66 @@ 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 $@ \
|
||||
) \
|
||||
) \
|
||||
)
|
||||
objects := libco emulator
|
||||
|
||||
%.o: $<; $(call compile)
|
||||
obj/libco.o: ../libco/libco.c
|
||||
obj/emulator.o: emulator/emulator.cpp
|
||||
|
||||
all: build;
|
||||
ifeq ($(target),higan)
|
||||
cores := fc sfc ms md pce msx gb gba ws ngp
|
||||
endif
|
||||
|
||||
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)
|
||||
ifeq ($(target),bsnes)
|
||||
cores := sfc gb
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),fc),)
|
||||
include fc/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),sfc),)
|
||||
include sfc/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),ms),)
|
||||
include ms/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),md),)
|
||||
include md/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),pce),)
|
||||
include pce/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),msx),)
|
||||
include msx/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),gb),)
|
||||
include gb/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),gba),)
|
||||
include gba/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),ws),)
|
||||
include ws/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),ngp),)
|
||||
include ngp/GNUmakefile
|
||||
endif
|
||||
|
||||
include processor/GNUmakefile
|
||||
|
||||
flags += $(foreach c,$(call strupper,$(cores)),-DCORE_$c)
|
||||
ui := target-$(target)
|
||||
include $(ui)/GNUmakefile
|
||||
-include obj/*.d
|
||||
|
||||
clean:
|
||||
-@$(call delete,out/*)
|
||||
-@$(call delete,obj/*)
|
||||
$(call delete,obj/*)
|
||||
$(call delete,out/*)
|
||||
|
@@ -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,34 +1,16 @@
|
||||
#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);
|
||||
}
|
||||
Audio::~Audio() {
|
||||
reset(nullptr);
|
||||
}
|
||||
|
||||
auto Audio::setInterface(Interface* interface) -> void {
|
||||
auto Audio::reset(Interface* interface) -> void {
|
||||
this->interface = interface;
|
||||
streams.reset();
|
||||
channels = 0;
|
||||
}
|
||||
|
||||
auto Audio::setFrequency(double frequency) -> void {
|
||||
@@ -46,11 +28,8 @@ 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> {
|
||||
this->channels = max(this->channels, channels);
|
||||
shared_pointer<Stream> stream = new Stream;
|
||||
stream->reset(channels, frequency, this->frequency);
|
||||
streams.append(stream);
|
||||
@@ -58,7 +37,7 @@ auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stre
|
||||
}
|
||||
|
||||
auto Audio::process() -> void {
|
||||
while(true) {
|
||||
while(streams) {
|
||||
for(auto& stream : streams) {
|
||||
if(!stream->pending()) return;
|
||||
}
|
||||
@@ -67,7 +46,7 @@ auto Audio::process() -> void {
|
||||
for(auto& sample : samples) sample = 0.0;
|
||||
|
||||
for(auto& stream : streams) {
|
||||
double buffer[16];
|
||||
double buffer[channels];
|
||||
uint length = stream->read(buffer), offset = 0;
|
||||
|
||||
for(auto& sample : samples) {
|
||||
@@ -78,13 +57,6 @@ auto Audio::process() -> void {
|
||||
|
||||
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) {
|
||||
@@ -92,7 +64,7 @@ auto Audio::process() -> void {
|
||||
if(balance > 0.0) samples[0] *= 1.0 - balance;
|
||||
}
|
||||
|
||||
platform->audioSample(samples, channels);
|
||||
platform->audioFrame(samples, channels);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/dsp/iir/dc-removal.hpp>
|
||||
#include <nall/dsp/iir/one-pole.hpp>
|
||||
#include <nall/dsp/iir/biquad.hpp>
|
||||
#include <nall/dsp/resampler/cubic.hpp>
|
||||
@@ -12,13 +13,12 @@ struct Filter;
|
||||
struct Stream;
|
||||
|
||||
struct Audio {
|
||||
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
|
||||
auto setInterface(Interface* interface) -> void;
|
||||
~Audio();
|
||||
auto reset(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>;
|
||||
|
||||
@@ -29,24 +29,22 @@ private:
|
||||
vector<shared_pointer<Stream>> streams;
|
||||
|
||||
uint channels = 0;
|
||||
double frequency = 0.0;
|
||||
double frequency = 48000.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 };
|
||||
enum class Mode : uint { DCRemoval, OnePole, Biquad } mode;
|
||||
enum class Type : uint { None, LowPass, HighPass } type;
|
||||
enum class Order : uint { None, First, Second } order;
|
||||
|
||||
Order order;
|
||||
DSP::IIR::OnePole onePole; //first-order
|
||||
DSP::IIR::Biquad biquad; //second-order
|
||||
DSP::IIR::DCRemoval dcRemoval;
|
||||
DSP::IIR::OnePole onePole;
|
||||
DSP::IIR::Biquad biquad;
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
@@ -54,7 +52,9 @@ struct Stream {
|
||||
|
||||
auto setFrequency(double inputFrequency, maybe<double> outputFrequency = nothing) -> void;
|
||||
|
||||
auto addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes = 1) -> void;
|
||||
auto addDCRemovalFilter() -> void;
|
||||
auto addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void;
|
||||
auto addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void;
|
||||
|
||||
auto pending() const -> bool;
|
||||
auto read(double samples[]) -> uint;
|
||||
@@ -68,6 +68,7 @@ struct Stream {
|
||||
private:
|
||||
struct Channel {
|
||||
vector<Filter> filters;
|
||||
vector<DSP::IIR::Biquad> nyquist;
|
||||
DSP::Resampler::Cubic resampler;
|
||||
};
|
||||
vector<Channel> channels;
|
106
higan/emulator/audio/stream.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
auto Stream::reset(uint channelCount, double inputFrequency, double outputFrequency) -> void {
|
||||
channels.reset();
|
||||
channels.resize(channelCount);
|
||||
|
||||
for(auto& channel : channels) {
|
||||
channel.filters.reset();
|
||||
}
|
||||
|
||||
setFrequency(inputFrequency, outputFrequency);
|
||||
}
|
||||
|
||||
auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void {
|
||||
this->inputFrequency = inputFrequency;
|
||||
if(outputFrequency) this->outputFrequency = outputFrequency();
|
||||
|
||||
for(auto& channel : channels) {
|
||||
channel.nyquist.reset();
|
||||
channel.resampler.reset(this->inputFrequency, this->outputFrequency);
|
||||
}
|
||||
|
||||
if(this->inputFrequency >= this->outputFrequency * 2) {
|
||||
//add a low-pass filter to prevent aliasing during resampling
|
||||
double cutoffFrequency = min(25000.0, this->outputFrequency / 2.0 - 2000.0);
|
||||
for(auto& channel : channels) {
|
||||
uint passes = 3;
|
||||
for(uint pass : range(passes)) {
|
||||
DSP::IIR::Biquad filter;
|
||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, this->inputFrequency, q);
|
||||
channel.nyquist.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addDCRemovalFilter() -> void {
|
||||
return; //todo: test to ensure this is desirable before enabling
|
||||
for(auto& channel : channels) {
|
||||
Filter filter{Filter::Mode::DCRemoval, Filter::Type::None, Filter::Order::None};
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
|
||||
for(auto& channel : channels) {
|
||||
for(uint pass : range(passes)) {
|
||||
if(order == Filter::Order::First) {
|
||||
Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First};
|
||||
filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, inputFrequency);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
if(order == Filter::Order::Second) {
|
||||
Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second};
|
||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
|
||||
for(auto& channel : channels) {
|
||||
for(uint pass : range(passes)) {
|
||||
if(order == Filter::Order::First) {
|
||||
Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First};
|
||||
filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, inputFrequency);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
if(order == Filter::Order::Second) {
|
||||
Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second};
|
||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::pending() const -> bool {
|
||||
return channels && channels[0].resampler.pending();
|
||||
}
|
||||
|
||||
auto Stream::read(double samples[]) -> uint {
|
||||
for(uint c : range(channels.size())) samples[c] = channels[c].resampler.read();
|
||||
return channels.size();
|
||||
}
|
||||
|
||||
auto Stream::write(const double samples[]) -> void {
|
||||
for(auto c : range(channels.size())) {
|
||||
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
|
||||
for(auto& filter : channels[c].filters) {
|
||||
switch(filter.mode) {
|
||||
case Filter::Mode::DCRemoval: sample = filter.dcRemoval.process(sample); break;
|
||||
case Filter::Mode::OnePole: sample = filter.onePole.process(sample); break;
|
||||
case Filter::Mode::Biquad: sample = filter.biquad.process(sample); break;
|
||||
}
|
||||
}
|
||||
for(auto& filter : channels[c].nyquist) {
|
||||
sample = filter.process(sample);
|
||||
}
|
||||
channels[c].resampler.write(sample);
|
||||
}
|
||||
|
||||
audio.process();
|
||||
}
|
@@ -17,11 +17,11 @@ struct Cheat {
|
||||
codes.reset();
|
||||
}
|
||||
|
||||
auto append(uint addr, uint data, maybe<uint> comp = nothing) -> void {
|
||||
auto append(uint addr, uint data, maybe<uint> comp = {}) -> void {
|
||||
codes.append({addr, data, comp});
|
||||
}
|
||||
|
||||
auto assign(const string_vector& list) -> void {
|
||||
auto assign(const vector<string>& list) -> void {
|
||||
reset();
|
||||
for(auto& entry : list) {
|
||||
for(auto code : entry.split("+")) {
|
||||
|
@@ -1,5 +1,9 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
#include <emulator/audio/audio.cpp>
|
||||
#include <emulator/video/video.cpp>
|
||||
#include <emulator/resource/resource.cpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
Platform* platform = nullptr;
|
||||
|
@@ -1,24 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/adaptive-array.hpp>
|
||||
#include <nall/any.hpp>
|
||||
#include <nall/bit-field.hpp>
|
||||
#include <nall/chrono.hpp>
|
||||
#include <nall/dl.hpp>
|
||||
#include <nall/endian.hpp>
|
||||
#include <nall/image.hpp>
|
||||
#include <nall/literals.hpp>
|
||||
#include <nall/random.hpp>
|
||||
#include <nall/serializer.hpp>
|
||||
#include <nall/shared-pointer.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
#include <nall/unique-pointer.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
#include <nall/vfs.hpp>
|
||||
#include <nall/hash/crc32.hpp>
|
||||
#include <nall/hash/sha256.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include "types.hpp"
|
||||
#include <libco/libco.h>
|
||||
#include <audio/audio.hpp>
|
||||
#include <video/video.hpp>
|
||||
#include <resource/resource.hpp>
|
||||
#include <emulator/types.hpp>
|
||||
#include <emulator/memory/readable.hpp>
|
||||
#include <emulator/memory/writable.hpp>
|
||||
#include <emulator/audio/audio.hpp>
|
||||
#include <emulator/video/video.hpp>
|
||||
#include <emulator/resource/resource.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "105";
|
||||
static const string Version = "107";
|
||||
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";
|
||||
static const string SerializerVersion = "107";
|
||||
|
||||
namespace Constants {
|
||||
namespace Colorburst {
|
||||
@@ -38,3 +57,4 @@ namespace Emulator {
|
||||
|
||||
#include "platform.hpp"
|
||||
#include "interface.hpp"
|
||||
#include "game.hpp"
|
||||
|
110
higan/emulator/game.hpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Game {
|
||||
struct Memory;
|
||||
struct Oscillator;
|
||||
|
||||
inline auto load(string_view) -> void;
|
||||
inline auto memory(Markup::Node) -> maybe<Memory>;
|
||||
inline auto oscillator(natural = 0) -> maybe<Oscillator>;
|
||||
|
||||
struct Memory {
|
||||
Memory() = default;
|
||||
inline Memory(Markup::Node);
|
||||
explicit operator bool() const { return (bool)type; }
|
||||
inline auto name() const -> string;
|
||||
|
||||
string type;
|
||||
natural size;
|
||||
string content;
|
||||
string manufacturer;
|
||||
string architecture;
|
||||
string identifier;
|
||||
boolean nonVolatile;
|
||||
};
|
||||
|
||||
struct Oscillator {
|
||||
Oscillator() = default;
|
||||
inline Oscillator(Markup::Node);
|
||||
explicit operator bool() const { return frequency; }
|
||||
|
||||
natural frequency;
|
||||
};
|
||||
|
||||
Markup::Node document;
|
||||
string sha256;
|
||||
string label;
|
||||
string name;
|
||||
string region;
|
||||
string revision;
|
||||
string board;
|
||||
vector<Memory> memoryList;
|
||||
vector<Oscillator> oscillatorList;
|
||||
};
|
||||
|
||||
auto Game::load(string_view text) -> void {
|
||||
document = BML::unserialize(text);
|
||||
|
||||
sha256 = document["game/sha256"].text();
|
||||
label = document["game/label"].text();
|
||||
name = document["game/name"].text();
|
||||
region = document["game/region"].text();
|
||||
revision = document["game/revision"].text();
|
||||
board = document["game/board"].text();
|
||||
|
||||
for(auto node : document.find("game/board/memory")) {
|
||||
memoryList.append(Memory{node});
|
||||
}
|
||||
|
||||
for(auto node : document.find("game/board/oscillator")) {
|
||||
oscillatorList.append(Oscillator{node});
|
||||
}
|
||||
}
|
||||
|
||||
auto Game::memory(Markup::Node node) -> maybe<Memory> {
|
||||
if(!node) return nothing;
|
||||
for(auto& memory : memoryList) {
|
||||
auto type = node["type"].text();
|
||||
auto size = node["size"].natural();
|
||||
auto content = node["content"].text();
|
||||
auto manufacturer = node["manufacturer"].text();
|
||||
auto architecture = node["architecture"].text();
|
||||
auto identifier = node["identifier"].text();
|
||||
if(type && type != memory.type) continue;
|
||||
if(size && size != memory.size) continue;
|
||||
if(content && content != memory.content) continue;
|
||||
if(manufacturer && manufacturer != memory.manufacturer) continue;
|
||||
if(architecture && architecture != memory.architecture) continue;
|
||||
if(identifier && identifier != memory.identifier) continue;
|
||||
return memory;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto Game::oscillator(natural index) -> maybe<Oscillator> {
|
||||
if(index < oscillatorList.size()) return oscillatorList[index];
|
||||
return nothing;
|
||||
}
|
||||
|
||||
Game::Memory::Memory(Markup::Node node) {
|
||||
type = node["type"].text();
|
||||
size = node["size"].natural();
|
||||
content = node["content"].text();
|
||||
manufacturer = node["manufacturer"].text();
|
||||
architecture = node["architecture"].text();
|
||||
identifier = node["identifier"].text();
|
||||
nonVolatile = !(bool)node["volatile"];
|
||||
}
|
||||
|
||||
auto Game::Memory::name() const -> string {
|
||||
if(architecture) return string{architecture, ".", content, ".", type}.downcase();
|
||||
return string{content, ".", type}.downcase();
|
||||
}
|
||||
|
||||
Game::Oscillator::Oscillator(Markup::Node node) {
|
||||
frequency = node["frequency"].natural();
|
||||
}
|
||||
|
||||
}
|
@@ -6,79 +6,96 @@ struct Interface {
|
||||
struct Information {
|
||||
string manufacturer;
|
||||
string name;
|
||||
bool overscan;
|
||||
} 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;
|
||||
string extension;
|
||||
bool resettable = false;
|
||||
};
|
||||
|
||||
struct Port {
|
||||
uint id;
|
||||
struct Display {
|
||||
struct Type { enum : uint {
|
||||
CRT,
|
||||
LCD,
|
||||
};};
|
||||
uint id = 0;
|
||||
string name;
|
||||
vector<Device> devices;
|
||||
};
|
||||
vector<Port> ports;
|
||||
|
||||
//information
|
||||
virtual auto manifest() -> string = 0;
|
||||
virtual auto title() -> string = 0;
|
||||
|
||||
struct VideoInformation {
|
||||
uint type = 0;
|
||||
uint colors = 0;
|
||||
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
|
||||
struct Port {
|
||||
uint id;
|
||||
string name;
|
||||
};
|
||||
|
||||
struct Device {
|
||||
uint id;
|
||||
string name;
|
||||
};
|
||||
|
||||
struct Input {
|
||||
struct Type { enum : uint {
|
||||
Hat,
|
||||
Button,
|
||||
Trigger,
|
||||
Control,
|
||||
Axis,
|
||||
Rumble,
|
||||
};};
|
||||
|
||||
uint type;
|
||||
string name;
|
||||
};
|
||||
|
||||
//information
|
||||
virtual auto information() -> Information { return {}; }
|
||||
|
||||
virtual auto display() -> Display { return {}; }
|
||||
virtual auto color(uint32 color) -> uint64 { return 0; }
|
||||
|
||||
//game interface
|
||||
virtual auto loaded() -> bool { return false; }
|
||||
virtual auto sha256() -> string { return ""; }
|
||||
virtual auto load(uint id) -> bool { return false; }
|
||||
virtual auto hashes() -> vector<string> { return {}; }
|
||||
virtual auto manifests() -> vector<string> { return {}; }
|
||||
virtual auto titles() -> vector<string> { return {}; }
|
||||
virtual auto load() -> bool { return false; }
|
||||
virtual auto save() -> void {}
|
||||
virtual auto unload() -> void {}
|
||||
|
||||
//system interface
|
||||
virtual auto ports() -> vector<Port> { return {}; }
|
||||
virtual auto devices(uint port) -> vector<Device> { return {}; }
|
||||
virtual auto inputs(uint device) -> vector<Input> { return {}; }
|
||||
virtual auto connected(uint port) -> uint { return 0; }
|
||||
virtual auto connect(uint port, uint device) -> void {}
|
||||
virtual auto power() -> void {}
|
||||
virtual auto reset() -> void {}
|
||||
virtual auto run() -> void {}
|
||||
|
||||
//time functions
|
||||
virtual auto rtc() -> bool { return false; }
|
||||
virtual auto rtcSynchronize() -> void {}
|
||||
virtual auto synchronize(uint64 timestamp = 0) -> void {}
|
||||
|
||||
//state functions
|
||||
virtual auto serialize() -> serializer = 0;
|
||||
virtual auto unserialize(serializer&) -> bool = 0;
|
||||
virtual auto serialize() -> serializer { return {}; }
|
||||
virtual auto unserialize(serializer&) -> bool { return false; }
|
||||
|
||||
//cheat functions
|
||||
virtual auto cheatSet(const string_vector& = {}) -> void {}
|
||||
virtual auto cheats(const vector<string>& = {}) -> void {}
|
||||
|
||||
//configuration
|
||||
virtual auto configuration() -> string { return {}; }
|
||||
virtual auto configuration(string name) -> string { return {}; }
|
||||
virtual auto configure(string configuration = "") -> bool { return false; }
|
||||
virtual auto configure(string name, string value) -> bool { return false; }
|
||||
|
||||
//settings
|
||||
virtual auto cap(const string& name) -> bool { return false; }
|
||||
virtual auto get(const string& name) -> any { return {}; }
|
||||
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
||||
|
||||
//shared functions
|
||||
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
|
||||
};
|
||||
|
||||
}
|
||||
|
30
higan/emulator/memory/memory.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator::Memory {
|
||||
|
||||
inline auto mirror(uint address, uint size) -> uint {
|
||||
if(size == 0) return 0;
|
||||
uint base = 0;
|
||||
uint mask = 1 << 31;
|
||||
while(address >= size) {
|
||||
while(!(address & mask)) mask >>= 1;
|
||||
address -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
return base + address;
|
||||
}
|
||||
|
||||
inline auto reduce(uint address, uint mask) -> uint {
|
||||
while(mask) {
|
||||
uint bits = (mask & -mask) - 1;
|
||||
address = address >> 1 & ~bits | address & bits;
|
||||
mask = (mask & mask - 1) >> 1;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
}
|
63
higan/emulator/memory/readable.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <emulator/memory/memory.hpp>
|
||||
|
||||
namespace Emulator::Memory {
|
||||
|
||||
template<typename T>
|
||||
struct Readable {
|
||||
~Readable() { reset(); }
|
||||
|
||||
inline auto reset() -> void {
|
||||
delete[] self.data;
|
||||
self.data = nullptr;
|
||||
self.size = 0;
|
||||
self.mask = 0;
|
||||
}
|
||||
|
||||
inline auto allocate(uint size, T fill = ~0ull) -> void {
|
||||
if(!size) return reset();
|
||||
delete[] self.data;
|
||||
self.size = size;
|
||||
self.mask = bit::round(self.size) - 1;
|
||||
self.data = new T[self.mask + 1];
|
||||
memory::fill<T>(self.data, self.mask + 1, fill);
|
||||
}
|
||||
|
||||
inline auto load(vfs::shared::file fp) -> void {
|
||||
fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
|
||||
for(uint address = self.size; address <= self.mask; address++) {
|
||||
self.data[address] = self.data[mirror(address, self.size)];
|
||||
}
|
||||
}
|
||||
|
||||
inline auto save(vfs::shared::file fp) -> void {
|
||||
fp->write(self.data, self.size * sizeof(T));
|
||||
}
|
||||
|
||||
explicit operator bool() const { return (bool)self.data; }
|
||||
inline auto data() const -> const T* { return self.data; }
|
||||
inline auto size() const -> uint { return self.size; }
|
||||
inline auto mask() const -> uint { return self.mask; }
|
||||
|
||||
inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
|
||||
inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
|
||||
inline auto write(uint address, T data) const -> void {}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
const uint size = self.size;
|
||||
s.integer(self.size);
|
||||
s.integer(self.mask);
|
||||
if(self.size != size) allocate(self.size);
|
||||
s.array(self.data, self.size);
|
||||
}
|
||||
|
||||
private:
|
||||
struct {
|
||||
T* data = nullptr;
|
||||
uint size = 0;
|
||||
uint mask = 0;
|
||||
} self;
|
||||
};
|
||||
|
||||
}
|
65
higan/emulator/memory/writable.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <emulator/memory/memory.hpp>
|
||||
|
||||
namespace Emulator::Memory {
|
||||
|
||||
template<typename T>
|
||||
struct Writable {
|
||||
~Writable() { reset(); }
|
||||
|
||||
inline auto reset() -> void {
|
||||
delete[] self.data;
|
||||
self.data = nullptr;
|
||||
self.size = 0;
|
||||
self.mask = 0;
|
||||
}
|
||||
|
||||
inline auto allocate(uint size, T fill = ~0ull) -> void {
|
||||
if(!size) return reset();
|
||||
delete[] self.data;
|
||||
self.size = size;
|
||||
self.mask = bit::round(self.size) - 1;
|
||||
self.data = new T[self.mask + 1];
|
||||
memory::fill<T>(self.data, self.mask + 1, fill);
|
||||
}
|
||||
|
||||
inline auto load(vfs::shared::file fp) -> void {
|
||||
fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
|
||||
for(uint address = self.size; address <= self.mask; address++) {
|
||||
self.data[address] = self.data[mirror(address, self.size)];
|
||||
}
|
||||
}
|
||||
|
||||
inline auto save(vfs::shared::file fp) -> void {
|
||||
fp->write(self.data, self.size * sizeof(T));
|
||||
}
|
||||
|
||||
explicit operator bool() const { return (bool)self.data; }
|
||||
inline auto data() -> T* { return self.data; }
|
||||
inline auto data() const -> const T* { return self.data; }
|
||||
inline auto size() const -> uint { return self.size; }
|
||||
inline auto mask() const -> uint { return self.mask; }
|
||||
|
||||
inline auto operator[](uint address) -> T& { return self.data[address & self.mask]; }
|
||||
inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
|
||||
inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
|
||||
inline auto write(uint address, T data) -> void { self.data[address & self.mask] = data; }
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
const uint size = self.size;
|
||||
s.integer(self.size);
|
||||
s.integer(self.mask);
|
||||
if(self.size != size) allocate(self.size);
|
||||
s.array(self.data, self.size);
|
||||
}
|
||||
|
||||
private:
|
||||
struct {
|
||||
T* data = nullptr;
|
||||
uint size = 0;
|
||||
uint mask = 0;
|
||||
} self;
|
||||
};
|
||||
|
||||
}
|
@@ -4,27 +4,24 @@ namespace Emulator {
|
||||
|
||||
struct Platform {
|
||||
struct Load {
|
||||
Load() : _pathID(nothing) {}
|
||||
Load(uint pathID, string option = "") : _pathID(pathID), _option(option) {}
|
||||
Load() = default;
|
||||
Load(uint pathID, string option = "") : valid(true), pathID(pathID), option(option) {}
|
||||
explicit operator bool() const { return valid; }
|
||||
|
||||
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;
|
||||
bool valid = false;
|
||||
uint pathID = 0;
|
||||
string option;
|
||||
};
|
||||
|
||||
virtual auto path(uint id) -> string { return ""; }
|
||||
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> 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 load(uint id, string name, string type, vector<string> options = {}) -> Load { return {}; }
|
||||
virtual auto videoFrame(const uint32* data, uint pitch, uint width, uint height) -> void {}
|
||||
virtual auto audioFrame(const double* samples, uint channels) -> void {}
|
||||
virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
|
||||
virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}
|
||||
virtual auto dipSettings(Markup::Node node) -> uint { return 0; }
|
||||
virtual auto notify(string text) -> void { print(text, "\n"); }
|
||||
virtual auto notify(string text) -> void {}
|
||||
};
|
||||
|
||||
extern Platform* platform;
|
||||
|
@@ -1,6 +1,4 @@
|
||||
namespace name=Resource
|
||||
namespace name=Logo
|
||||
binary name=higan file=logo/higan.png
|
||||
namespace name=Sprite
|
||||
binary name=CrosshairRed file=sprite/crosshair-red.png
|
||||
binary name=CrosshairGreen file=sprite/crosshair-green.png
|
45
higan/emulator/resource/resource.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "resource.hpp"
|
||||
|
||||
namespace Resource {
|
||||
namespace Sprite {
|
||||
const unsigned char CrosshairRed[342] = {
|
||||
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
|
||||
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
|
||||
196,1,149,43,14,27,0,0,0,248,73,68,65,84,88,133,205,87,65,14,196,32,8,132,102,255,255,101,246,176,177,139,
|
||||
148,81,80,27,229,212,70,102,6,212,0,50,229,77,26,107,156,37,139,2,228,241,209,39,11,113,71,156,68,139,106,128,
|
||||
56,255,198,175,203,223,114,16,79,68,253,138,90,99,141,113,112,80,231,131,196,11,83,52,19,43,196,53,135,147,7,38,
|
||||
150,104,244,212,32,86,235,228,236,20,6,200,207,191,117,215,70,12,242,94,139,133,166,236,173,236,67,252,111,139,67,157,
|
||||
237,71,48,27,192,244,142,93,228,23,148,144,184,228,131,96,254,3,164,4,176,213,108,37,52,5,208,53,47,227,81,28,
|
||||
49,153,102,163,88,96,149,68,150,193,21,223,59,128,68,43,69,13,103,4,199,246,8,34,151,240,209,249,38,112,251,47,
|
||||
97,177,209,74,152,246,95,93,9,211,51,160,181,99,142,128,104,115,55,124,59,136,115,7,146,237,51,33,2,71,166,226,
|
||||
94,23,13,77,214,104,44,103,174,163,143,86,189,244,187,224,232,151,81,21,132,39,210,33,91,246,54,132,193,44,226,219,
|
||||
107,95,57,136,120,253,172,254,16,23,0,0,0,0,73,69,78,68,174,66,96,130,
|
||||
};
|
||||
const unsigned char CrosshairGreen[329] = {
|
||||
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
|
||||
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
|
||||
196,1,149,43,14,27,0,0,0,235,73,68,65,84,88,133,213,87,65,18,195,32,8,196,78,31,230,211,253,153,61,180,
|
||||
52,18,145,1,193,97,178,39,141,44,139,24,69,11,216,209,133,177,98,117,166,37,92,162,77,176,170,118,223,26,163,78,
|
||||
68,71,145,198,244,169,157,57,35,84,248,43,222,255,109,154,254,113,140,114,102,222,18,239,165,120,251,181,42,0,232,103,
|
||||
114,217,85,226,163,27,124,232,163,87,142,115,153,82,137,71,98,233,247,21,44,228,194,169,217,171,252,159,22,95,234,164,
|
||||
47,129,55,128,144,140,237,166,63,132,151,190,4,247,147,16,103,35,157,90,220,140,119,121,80,224,94,108,0,164,227,119,
|
||||
182,221,229,13,182,82,193,225,176,42,56,59,188,105,9,52,5,3,109,58,243,205,202,203,255,9,17,251,91,202,169,227,
|
||||
205,128,235,198,19,17,64,40,82,171,225,233,32,158,113,33,65,164,222,9,105,16,50,81,55,238,88,210,212,119,1,0,
|
||||
238,241,241,126,143,125,62,216,173,151,209,35,222,134,235,96,98,252,229,226,3,112,72,179,236,202,138,114,18,0,0,0,
|
||||
0,73,69,78,68,174,66,96,130,
|
||||
};
|
||||
const unsigned char CrosshairBlue[332] = {
|
||||
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
|
||||
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
|
||||
196,1,149,43,14,27,0,0,0,238,73,68,65,84,88,133,213,87,91,18,195,32,8,196,78,15,232,81,189,161,253,9,
|
||||
25,52,98,121,57,76,246,43,137,44,11,24,69,11,232,209,55,99,69,235,76,74,184,69,107,229,245,91,27,220,137,124,
|
||||
75,140,58,21,165,34,181,246,199,251,100,167,174,200,32,124,137,119,124,134,177,252,116,108,224,44,120,44,190,156,56,102,
|
||||
163,204,228,182,107,173,80,31,93,225,67,30,189,112,124,85,41,145,120,36,88,191,159,96,33,23,78,101,47,242,127,90,
|
||||
156,213,73,159,2,111,0,33,21,179,150,63,132,151,62,5,243,78,136,217,236,118,173,85,198,86,30,20,152,154,13,192,
|
||||
118,251,125,216,90,121,212,118,215,112,86,224,26,142,133,247,152,2,73,195,64,155,190,248,166,229,229,255,132,8,243,146,
|
||||
242,234,120,43,224,58,241,68,4,16,138,212,110,120,58,136,119,28,72,16,169,103,194,33,136,63,68,209,184,103,74,83,
|
||||
239,5,0,215,26,167,231,123,124,103,130,53,221,140,94,113,55,100,131,9,242,151,139,31,79,50,234,237,105,206,30,22,
|
||||
0,0,0,0,73,69,78,68,174,66,96,130,
|
||||
};
|
||||
}
|
||||
}
|
7
higan/emulator/resource/resource.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Resource {
|
||||
namespace Sprite {
|
||||
extern const unsigned char CrosshairRed[342];
|
||||
extern const unsigned char CrosshairGreen[329];
|
||||
extern const unsigned char CrosshairBlue[332];
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 332 B |
Before Width: | Height: | Size: 329 B After Width: | Height: | Size: 329 B |
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 342 B |
@@ -15,6 +15,10 @@ struct Thread {
|
||||
inline auto scalar() const { return _scalar; }
|
||||
inline auto clock() const { return _clock; }
|
||||
|
||||
auto setHandle(cothread_t handle) -> void {
|
||||
_handle = handle;
|
||||
}
|
||||
|
||||
auto setFrequency(double frequency) -> void {
|
||||
_frequency = frequency + 0.5;
|
||||
_scalar = Second / _frequency;
|
||||
|
@@ -1,16 +1,15 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
#include "sprite.cpp"
|
||||
Video video;
|
||||
|
||||
Video::~Video() {
|
||||
reset();
|
||||
reset(nullptr);
|
||||
}
|
||||
|
||||
auto Video::reset() -> void {
|
||||
interface = nullptr;
|
||||
auto Video::reset(Interface* interface) -> void {
|
||||
this->interface = interface;
|
||||
|
||||
sprites.reset();
|
||||
delete buffer;
|
||||
buffer = nullptr;
|
||||
@@ -25,18 +24,14 @@ auto Video::reset() -> void {
|
||||
effects.rotateLeft = false;
|
||||
}
|
||||
|
||||
auto Video::setInterface(Interface* interface) -> void {
|
||||
this->interface = interface;
|
||||
}
|
||||
|
||||
auto Video::setPalette() -> void {
|
||||
if(!interface) return;
|
||||
|
||||
delete palette;
|
||||
colors = interface->videoColors();
|
||||
colors = interface->display().colors;
|
||||
palette = new uint32[colors];
|
||||
for(auto index : range(colors)) {
|
||||
uint64 color = interface->videoColor(index);
|
||||
uint64 color = interface->color(index);
|
||||
uint16 b = color.bits( 0,15);
|
||||
uint16 g = color.bits(16,31);
|
||||
uint16 r = color.bits(32,47);
|
||||
@@ -63,11 +58,17 @@ auto Video::setPalette() -> void {
|
||||
b = uclamp<16>(b * luminance);
|
||||
}
|
||||
|
||||
//convert color from 16-bits/channel to 8-bits/channel; force alpha to 1.0
|
||||
palette[index] = a.byte(1) << 24 | r.byte(1) << 16 | g.byte(1) << 8 | b.byte(1) << 0;
|
||||
switch(depth) {
|
||||
case 24: palette[index] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break;
|
||||
case 30: palette[index] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::setDepth(uint depth) -> void {
|
||||
this->depth = depth;
|
||||
}
|
||||
|
||||
auto Video::setSaturation(double saturation) -> void {
|
||||
this->saturation = saturation;
|
||||
}
|
||||
@@ -101,7 +102,7 @@ auto Video::createSprite(uint width, uint height) -> shared_pointer<Sprite> {
|
||||
}
|
||||
|
||||
auto Video::removeSprite(shared_pointer<Sprite> sprite) -> bool {
|
||||
for(uint n : range(sprites)) {
|
||||
for(uint n : range(sprites.size())) {
|
||||
if(sprite == sprites[n]) {
|
||||
sprites.remove(n);
|
||||
return true;
|
||||
@@ -133,21 +134,23 @@ auto Video::refresh(uint32* input, uint pitch, uint width, uint height) -> void
|
||||
*target++ = color;
|
||||
}
|
||||
} else {
|
||||
uint32 mask = depth == 30 ? 0x40100401 : 0x01010101;
|
||||
for(uint x : range(width)) {
|
||||
auto a = *target;
|
||||
auto b = palette[*source++];
|
||||
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
*target++ = (a + b - ((a ^ b) & mask)) >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(effects.colorBleed) {
|
||||
uint32 mask = depth == 30 ? 0x40100401 : 0x01010101;
|
||||
for(uint y : range(height)) {
|
||||
auto target = output + y * width;
|
||||
for(uint x : range(width)) {
|
||||
auto a = target[x];
|
||||
auto b = target[x + (x != width - 1)];
|
||||
target[x] = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
target[x] = (a + b - ((a ^ b) & mask)) >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,6 +170,7 @@ auto Video::refresh(uint32* input, uint pitch, uint width, uint height) -> void
|
||||
for(auto& sprite : sprites) {
|
||||
if(!sprite->visible) continue;
|
||||
|
||||
uint32 opaqueAlpha = depth == 30 ? 0xc0000000 : 0xff000000;
|
||||
for(int y : range(sprite->height)) {
|
||||
for(int x : range(sprite->width)) {
|
||||
int pixelY = sprite->y + y;
|
||||
@@ -176,12 +180,12 @@ auto Video::refresh(uint32* input, uint pitch, uint width, uint height) -> void
|
||||
if(pixelX < 0 || pixelX >= width) continue;
|
||||
|
||||
auto pixel = sprite->pixels[y * sprite->width + x];
|
||||
if(pixel) output[pixelY * width + pixelX] = 0xff000000 | pixel;
|
||||
if(pixel) output[pixelY * width + pixelX] = opaqueAlpha | pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
platform->videoRefresh(output, width * sizeof(uint32), width, height);
|
||||
platform->videoFrame(output, width * sizeof(uint32), width, height);
|
||||
}
|
||||
|
||||
}
|
@@ -14,11 +14,10 @@ struct Video {
|
||||
};
|
||||
|
||||
~Video();
|
||||
|
||||
auto reset() -> void;
|
||||
auto setInterface(Interface* interface) -> void;
|
||||
auto reset(Interface* interface) -> void;
|
||||
|
||||
auto setPalette() -> void;
|
||||
auto setDepth(uint depth) -> void;
|
||||
auto setSaturation(double saturation) -> void;
|
||||
auto setGamma(double gamma) -> void;
|
||||
auto setLuminance(double luminance) -> void;
|
||||
@@ -42,6 +41,7 @@ private:
|
||||
uint height = 0;
|
||||
uint colors = 0;
|
||||
|
||||
uint depth = 24;
|
||||
double saturation = 1.0;
|
||||
double gamma = 1.0;
|
||||
double luminance = 1.0;
|
@@ -3,11 +3,11 @@ 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/)
|
||||
obj/fc-interface.o: fc/interface/interface.cpp
|
||||
obj/fc-system.o: fc/system/system.cpp
|
||||
obj/fc-controller.o: fc/controller/controller.cpp
|
||||
obj/fc-memory.o: fc/memory/memory.cpp
|
||||
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp
|
||||
obj/fc-cpu.o: fc/cpu/cpu.cpp
|
||||
obj/fc-apu.o: fc/apu/apu.cpp
|
||||
obj/fc-ppu.o: fc/ppu/ppu.cpp
|
||||
|
@@ -71,13 +71,13 @@ auto APU::setSample(int16 sample) -> void {
|
||||
cartridgeSample = sample;
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
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);
|
||||
stream->addHighPassFilter( 90.0, Emulator::Filter::Order::First);
|
||||
stream->addHighPassFilter( 440.0, Emulator::Filter::Order::First);
|
||||
stream->addLowPassFilter (14000.0, Emulator::Filter::Order::First);
|
||||
stream->addDCRemovalFilter();
|
||||
|
||||
pulse[0].power();
|
||||
pulse[1].power();
|
||||
|
@@ -12,7 +12,7 @@ struct APU : Thread {
|
||||
auto setIRQ() -> void;
|
||||
auto setSample(int16 sample) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
@@ -22,6 +22,7 @@ struct BandaiFCG : Board {
|
||||
case 2: return 0x0000 | (addr & 0x03ff);
|
||||
case 3: return 0x0400 | (addr & 0x03ff);
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
|
@@ -21,63 +21,57 @@
|
||||
|
||||
Board::Board(Markup::Node& document) {
|
||||
cartridge.board = this;
|
||||
auto board = document["board"];
|
||||
information.type = document["game/board"].text();
|
||||
|
||||
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)) {
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Program)"]}) {
|
||||
if(prgrom.size = memory.size) prgrom.data = new uint8_t[prgrom.size]();
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.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()));
|
||||
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
|
||||
if(prgram.size = memory.size) prgram.data = new uint8_t[prgram.size](), prgram.writable = true;
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read)) {
|
||||
fp->read(prgram.data, min(prgram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prgram.writable = true;
|
||||
chrram.writable = true;
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Character)"]}) {
|
||||
if(chrrom.size = memory.size) chrrom.data = new uint8_t[chrrom.size]();
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read, File::Required)) {
|
||||
fp->read(chrrom.data, min(chrrom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Character)"]}) {
|
||||
if(chrram.size = memory.size) chrram.data = new uint8_t[chrram.size](), chrram.writable = true;
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read)) {
|
||||
fp->read(chrram.data, min(chrram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.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);
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Character)"]}) {
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Write)) {
|
||||
fp->write(chrram.data, chrram.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,9 +132,9 @@ auto Board::serialize(serializer& s) -> void {
|
||||
|
||||
auto Board::load(string manifest) -> Board* {
|
||||
auto document = BML::unserialize(manifest);
|
||||
cartridge.information.title = document["information/title"].text();
|
||||
cartridge.information.title = document["game/label"].text();
|
||||
|
||||
string type = document["board/id"].text();
|
||||
string type = document["game/board"].text();
|
||||
|
||||
if(type == "BANDAI-FCG" ) return new BandaiFCG(document);
|
||||
|
||||
|
@@ -39,7 +39,6 @@ struct Board {
|
||||
|
||||
struct Information {
|
||||
string type;
|
||||
bool battery;
|
||||
} information;
|
||||
|
||||
Memory prgrom;
|
||||
|
@@ -151,6 +151,7 @@ struct Sunsoft5B : Board {
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //first
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //second
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
|
@@ -17,8 +17,8 @@ auto Cartridge::main() -> void {
|
||||
|
||||
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();
|
||||
information.pathID = loaded.pathID;
|
||||
information.region = loaded.option;
|
||||
} else return false;
|
||||
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
|
@@ -10,7 +10,7 @@ struct Cartridge : Thread {
|
||||
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto region() const -> string { return information.region; }
|
||||
auto sha256() const -> string { return information.sha256; }
|
||||
auto hash() const -> string { return information.sha256; }
|
||||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
|
@@ -34,6 +34,7 @@ struct MMC1 : Chip {
|
||||
case 2: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
case 3: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
|
@@ -35,6 +35,7 @@ struct MMC3 : Chip {
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
@@ -53,11 +54,13 @@ struct MMC3 : Chip {
|
||||
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
|
@@ -83,6 +83,8 @@ struct MMC5 : Chip {
|
||||
case 0x5205: return (multiplier * multiplicand) >> 0;
|
||||
case 0x5206: return (multiplier * multiplicand) >> 8;
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
@@ -215,6 +217,8 @@ struct MMC5 : Chip {
|
||||
auto bank = chrSpriteBank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto chrBGAddr(uint addr) -> uint {
|
||||
@@ -239,6 +243,8 @@ struct MMC5 : Chip {
|
||||
auto bank = chrBGBank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto chrVSAddr(uint addr) -> uint {
|
||||
@@ -274,6 +280,7 @@ struct MMC5 : Chip {
|
||||
case 2: return exramMode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
|
||||
case 3: return (hcounter & 2) == 0 ? fillmodeTile : fillmodeColor;
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
|
@@ -35,6 +35,7 @@ struct MMC6 : Chip {
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
@@ -53,11 +54,13 @@ struct MMC6 : Chip {
|
||||
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
|
@@ -115,6 +115,7 @@ struct VRC6 : Chip {
|
||||
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);
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
@@ -129,6 +130,7 @@ struct VRC6 : Chip {
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
|
@@ -96,6 +96,7 @@ struct VRC7 : Chip {
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
|
@@ -24,22 +24,21 @@ auto CPU::step(uint clocks) -> void {
|
||||
for(auto peripheral : peripherals) synchronize(*peripheral);
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
auto CPU::power(bool reset) -> void {
|
||||
MOS6502::BCD = 0;
|
||||
MOS6502::power();
|
||||
create(CPU::Enter, system.frequency());
|
||||
|
||||
for(auto addr : range(0x0800)) ram[addr] = 0xff;
|
||||
ram[0x0008] = 0xf7;
|
||||
ram[0x0009] = 0xef;
|
||||
ram[0x000a] = 0xdf;
|
||||
ram[0x000f] = 0xbf;
|
||||
if(!reset) for(auto& data : ram) data = 0xff;
|
||||
ram[0x008] = 0xf7; //todo: what is this about?
|
||||
ram[0x009] = 0xef;
|
||||
ram[0x00a] = 0xdf;
|
||||
ram[0x00f] = 0xbf;
|
||||
|
||||
r.pc.byte(0) = bus.read(0xfffc);
|
||||
r.pc.byte(1) = bus.read(0xfffd);
|
||||
|
||||
memory::fill(&io, sizeof(IO));
|
||||
io.rdyLine = 1;
|
||||
io = {};
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ struct CPU : Processor::MOS6502, Thread {
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto readRAM(uint11 addr) -> uint8;
|
||||
@@ -37,20 +37,20 @@ struct CPU : Processor::MOS6502, Thread {
|
||||
//protected:
|
||||
vector<Thread*> peripherals;
|
||||
|
||||
uint8 ram[0x0800];
|
||||
uint8 ram[0x800];
|
||||
|
||||
struct IO {
|
||||
bool interruptPending;
|
||||
bool nmiPending;
|
||||
bool nmiLine;
|
||||
bool irqLine;
|
||||
bool apuLine;
|
||||
bool interruptPending = 0;
|
||||
bool nmiPending = 0;
|
||||
bool nmiLine = 0;
|
||||
bool irqLine = 0;
|
||||
bool apuLine = 0;
|
||||
|
||||
bool rdyLine;
|
||||
bool rdyAddrValid;
|
||||
bool rdyLine = 1;
|
||||
bool rdyAddrValid = 0;
|
||||
uint16 rdyAddrValue;
|
||||
|
||||
bool oamdmaPending;
|
||||
bool oamdmaPending = 0;
|
||||
uint8 oamdmaPage;
|
||||
} io;
|
||||
};
|
||||
|
@@ -4,69 +4,35 @@ namespace Famicom {
|
||||
|
||||
Settings settings;
|
||||
|
||||
Interface::Interface() {
|
||||
auto Interface::information() -> Information {
|
||||
Information information;
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Famicom";
|
||||
information.overscan = true;
|
||||
|
||||
media.append({ID::Famicom, "Famicom", "fc"});
|
||||
|
||||
Port controllerPort1{ID::Port::Controller1, "Controller Port 1"};
|
||||
Port controllerPort2{ID::Port::Controller2, "Controller Port 2"};
|
||||
|
||||
{ Device device{ID::Device::None, "None"};
|
||||
controllerPort1.devices.append(device);
|
||||
controllerPort2.devices.append(device);
|
||||
}
|
||||
|
||||
{ Device device{ID::Device::Gamepad, "Gamepad"};
|
||||
device.inputs.append({0, "Up" });
|
||||
device.inputs.append({0, "Down" });
|
||||
device.inputs.append({0, "Left" });
|
||||
device.inputs.append({0, "Right" });
|
||||
device.inputs.append({0, "B" });
|
||||
device.inputs.append({0, "A" });
|
||||
device.inputs.append({0, "Select"});
|
||||
device.inputs.append({0, "Start" });
|
||||
controllerPort1.devices.append(device);
|
||||
controllerPort2.devices.append(device);
|
||||
}
|
||||
|
||||
ports.append(move(controllerPort1));
|
||||
ports.append(move(controllerPort2));
|
||||
information.extension = "fc";
|
||||
information.resettable = true;
|
||||
return information;
|
||||
}
|
||||
|
||||
auto Interface::manifest() -> string {
|
||||
return cartridge.manifest();
|
||||
auto Interface::display() -> Display {
|
||||
Display display;
|
||||
display.type = Display::Type::CRT;
|
||||
display.colors = 1 << 9;
|
||||
display.width = 256;
|
||||
display.height = 240;
|
||||
display.internalWidth = 256;
|
||||
display.internalHeight = 240;
|
||||
display.aspectCorrection = 8.0 / 7.0;
|
||||
return display;
|
||||
}
|
||||
|
||||
auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoInformation() -> VideoInformation {
|
||||
VideoInformation vi;
|
||||
vi.width = 256;
|
||||
vi.height = 240;
|
||||
vi.internalWidth = 256;
|
||||
vi.internalHeight = 240;
|
||||
vi.aspectCorrection = 8.0 / 7.0;
|
||||
vi.refreshRate = system.frequency() / (ppu.vlines() * ppu.rate() * 341.0);
|
||||
return vi;
|
||||
}
|
||||
|
||||
auto Interface::videoColors() -> uint32 {
|
||||
return 1 << 9;
|
||||
}
|
||||
|
||||
auto Interface::videoColor(uint32 n) -> uint64 {
|
||||
auto Interface::color(uint32 n) -> uint64 {
|
||||
double saturation = 2.0;
|
||||
double hue = 0.0;
|
||||
double contrast = 1.0;
|
||||
double brightness = 1.0;
|
||||
double gamma = settings.colorEmulation ? 1.8 : 2.2;
|
||||
|
||||
int color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
|
||||
int color = (n & 0x0f), level = color < 0xe ? int(n >> 4 & 3) : 1;
|
||||
|
||||
static const double black = 0.518, white = 1.962, attenuation = 0.746;
|
||||
static const double levels[8] = {
|
||||
@@ -114,11 +80,19 @@ auto Interface::loaded() -> bool {
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::sha256() -> string {
|
||||
return cartridge.sha256();
|
||||
auto Interface::hashes() -> vector<string> {
|
||||
return {cartridge.hash()};
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> bool {
|
||||
auto Interface::manifests() -> vector<string> {
|
||||
return {cartridge.manifest()};
|
||||
}
|
||||
|
||||
auto Interface::titles() -> vector<string> {
|
||||
return {cartridge.title()};
|
||||
}
|
||||
|
||||
auto Interface::load() -> bool {
|
||||
return system.load(this);
|
||||
}
|
||||
|
||||
@@ -131,13 +105,68 @@ auto Interface::unload() -> void {
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::ports() -> vector<Port> { return {
|
||||
{ID::Port::Controller1, "Controller Port 1"},
|
||||
{ID::Port::Controller2, "Controller Port 2"},
|
||||
{ID::Port::Expansion, "Expansion Port" }};
|
||||
}
|
||||
|
||||
auto Interface::devices(uint port) -> vector<Device> {
|
||||
if(port == ID::Port::Controller1) return {
|
||||
{ID::Device::None, "None" },
|
||||
{ID::Device::Gamepad, "Gamepad"}
|
||||
};
|
||||
|
||||
if(port == ID::Port::Controller2) return {
|
||||
{ID::Device::None, "None" },
|
||||
{ID::Device::Gamepad, "Gamepad"}
|
||||
};
|
||||
|
||||
if(port == ID::Port::Expansion) return {
|
||||
{ID::Device::None, "None"}
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Interface::inputs(uint device) -> vector<Input> {
|
||||
using Type = Input::Type;
|
||||
|
||||
if(device == ID::Device::None) return {
|
||||
};
|
||||
|
||||
if(device == ID::Device::Gamepad) return {
|
||||
{Type::Hat, "Up" },
|
||||
{Type::Hat, "Down" },
|
||||
{Type::Hat, "Left" },
|
||||
{Type::Hat, "Right" },
|
||||
{Type::Button, "B" },
|
||||
{Type::Button, "A" },
|
||||
{Type::Control, "Select"},
|
||||
{Type::Control, "Start" }
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Interface::connected(uint port) -> uint {
|
||||
if(port == ID::Port::Controller1) return settings.controllerPort1;
|
||||
if(port == ID::Port::Controller2) return settings.controllerPort2;
|
||||
if(port == ID::Port::Expansion) return settings.expansionPort;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto Interface::connect(uint port, uint device) -> void {
|
||||
if(port == ID::Port::Controller1) controllerPort1.connect(settings.controllerPort1 = device);
|
||||
if(port == ID::Port::Controller2) controllerPort2.connect(settings.controllerPort2 = device);
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
system.power();
|
||||
system.power(/* reset = */ false);
|
||||
}
|
||||
|
||||
auto Interface::reset() -> void {
|
||||
system.power(/* reset = */ true);
|
||||
}
|
||||
|
||||
auto Interface::run() -> void {
|
||||
@@ -153,7 +182,7 @@ auto Interface::unserialize(serializer& s) -> bool {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
auto Interface::cheatSet(const string_vector& list) -> void {
|
||||
auto Interface::cheats(const vector<string>& list) -> void {
|
||||
cheat.assign(list);
|
||||
}
|
||||
|
||||
@@ -172,7 +201,7 @@ auto Interface::get(const string& name) -> any {
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
Emulator::video.setPalette();
|
||||
return true;
|
||||
}
|
||||
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#if defined(CORE_FC)
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
struct ID {
|
||||
@@ -19,31 +21,33 @@ struct ID {
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
using Emulator::Interface::load;
|
||||
auto information() -> Information override;
|
||||
|
||||
Interface();
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
||||
auto videoInformation() -> VideoInformation override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
auto display() -> Display override;
|
||||
auto color(uint32 color) -> uint64 override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto sha256() -> string override;
|
||||
auto load(uint id) -> bool override;
|
||||
auto hashes() -> vector<string> override;
|
||||
auto manifests() -> vector<string> override;
|
||||
auto titles() -> vector<string> override;
|
||||
auto load() -> bool override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
auto ports() -> vector<Port> override;
|
||||
auto devices(uint port) -> vector<Device> override;
|
||||
auto inputs(uint device) -> vector<Input> override;
|
||||
|
||||
auto connected(uint port) -> uint override;
|
||||
auto connect(uint port, uint device) -> void override;
|
||||
auto power() -> void override;
|
||||
auto reset() -> void override;
|
||||
auto run() -> void override;
|
||||
|
||||
auto serialize() -> serializer override;
|
||||
auto unserialize(serializer&) -> bool override;
|
||||
|
||||
auto cheatSet(const string_vector&) -> void override;
|
||||
auto cheats(const vector<string>&) -> void override;
|
||||
|
||||
auto cap(const string& name) -> bool override;
|
||||
auto get(const string& name) -> any override;
|
||||
@@ -54,11 +58,13 @@ struct Settings {
|
||||
bool colorEmulation = true;
|
||||
bool scanlineEmulation = true;
|
||||
|
||||
uint controllerPort1 = 0;
|
||||
uint controllerPort2 = 0;
|
||||
uint expansionPort = 0;
|
||||
uint controllerPort1 = ID::Device::Gamepad;
|
||||
uint controllerPort2 = ID::Device::Gamepad;
|
||||
uint expansionPort = ID::Device::None;
|
||||
};
|
||||
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -54,18 +54,19 @@ auto PPU::refresh() -> void {
|
||||
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 240);
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
auto PPU::power(bool reset) -> void {
|
||||
create(PPU::Enter, system.frequency());
|
||||
|
||||
memory::fill(&io, sizeof(IO));
|
||||
memory::fill(&latch, sizeof(Latches));
|
||||
io.vramIncrement = 1;
|
||||
io = {};
|
||||
latch = {};
|
||||
|
||||
for(auto& n : ciram ) n = 0;
|
||||
for(auto& n : cgram ) n = 0;
|
||||
for(auto& n : oam ) n = 0;
|
||||
if(!reset) {
|
||||
for(auto& data : ciram ) data = 0;
|
||||
for(auto& data : cgram ) data = 0;
|
||||
for(auto& data : oam ) data = 0;
|
||||
}
|
||||
|
||||
for(auto& n : buffer) n = 0;
|
||||
for(auto& data : buffer) data = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ struct PPU : Thread {
|
||||
auto frame() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto readCIRAM(uint11 addr) -> uint8;
|
||||
@@ -39,13 +39,14 @@ struct PPU : Thread {
|
||||
uint8 mdr;
|
||||
|
||||
uint1 field;
|
||||
uint lx;
|
||||
uint ly;
|
||||
uint lx = 0;
|
||||
uint ly = 0;
|
||||
|
||||
uint8 busData;
|
||||
|
||||
union {
|
||||
uint value;
|
||||
union Union {
|
||||
auto& operator=(const Union& u) { value = u.value; return *this; }
|
||||
uint value = 0;
|
||||
NaturalBitField<uint, 0, 4> tileX;
|
||||
NaturalBitField<uint, 5, 9> tileY;
|
||||
NaturalBitField<uint,10,11> nametable;
|
||||
@@ -59,28 +60,28 @@ struct PPU : Thread {
|
||||
NaturalBitField<uint,16,18> fineX;
|
||||
} v, t;
|
||||
|
||||
bool nmiHold;
|
||||
bool nmiFlag;
|
||||
bool nmiHold = 0;
|
||||
bool nmiFlag = 0;
|
||||
|
||||
//$2000
|
||||
uint vramIncrement;
|
||||
uint spriteAddress;
|
||||
uint bgAddress;
|
||||
uint spriteHeight;
|
||||
bool masterSelect;
|
||||
bool nmiEnable;
|
||||
uint vramIncrement = 1;
|
||||
uint spriteAddress = 0;
|
||||
uint bgAddress = 0;
|
||||
uint spriteHeight = 0;
|
||||
bool masterSelect = 0;
|
||||
bool nmiEnable = 0;
|
||||
|
||||
//$2001
|
||||
bool grayscale;
|
||||
bool bgEdgeEnable;
|
||||
bool spriteEdgeEnable;
|
||||
bool bgEnable;
|
||||
bool spriteEnable;
|
||||
bool grayscale = 0;
|
||||
bool bgEdgeEnable = 0;
|
||||
bool spriteEdgeEnable = 0;
|
||||
bool bgEnable = 0;
|
||||
bool spriteEnable = 0;
|
||||
uint3 emphasis;
|
||||
|
||||
//$2002
|
||||
bool spriteOverflow;
|
||||
bool spriteZeroHit;
|
||||
bool spriteOverflow = 0;
|
||||
bool spriteZeroHit = 0;
|
||||
|
||||
//$2003
|
||||
uint8 oamAddress;
|
||||
@@ -106,8 +107,8 @@ struct PPU : Thread {
|
||||
uint16 tiledataLo;
|
||||
uint16 tiledataHi;
|
||||
|
||||
uint oamIterator;
|
||||
uint oamCounter;
|
||||
uint oamIterator = 0;
|
||||
uint oamCounter = 0;
|
||||
|
||||
OAM oam[8]; //primary
|
||||
OAM soam[8]; //secondary
|
||||
|
@@ -3,14 +3,11 @@ auto System::serialize() -> serializer {
|
||||
|
||||
uint signature = 0x31545342;
|
||||
char version[16] = {0};
|
||||
char hash[64] = {0};
|
||||
char description[512] = {0};
|
||||
memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size());
|
||||
memory::copy(&hash, (const char*)cartridge.sha256(), 64);
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serializeAll(s);
|
||||
@@ -20,18 +17,16 @@ auto System::serialize() -> serializer {
|
||||
auto System::unserialize(serializer& s) -> bool {
|
||||
uint signature;
|
||||
char version[16];
|
||||
char hash[64];
|
||||
char description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
if(signature != 0x31545342) return false;
|
||||
if(string{version} != Emulator::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
power(/* reset = */ false);
|
||||
serializeAll(s);
|
||||
return true;
|
||||
}
|
||||
@@ -54,12 +49,10 @@ auto System::serializeInit() -> void {
|
||||
|
||||
uint signature = 0;
|
||||
char version[16];
|
||||
char hash[64];
|
||||
char description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serializeAll(s);
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
Scheduler scheduler;
|
||||
@@ -62,20 +61,17 @@ auto System::unload() -> void {
|
||||
information.loaded = false;
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
auto System::power(bool reset) -> void {
|
||||
Emulator::video.reset(interface);
|
||||
Emulator::video.setPalette();
|
||||
|
||||
Emulator::audio.reset();
|
||||
Emulator::audio.setInterface(interface);
|
||||
Emulator::audio.reset(interface);
|
||||
|
||||
scheduler.reset();
|
||||
cartridge.power();
|
||||
cpu.power();
|
||||
apu.power();
|
||||
ppu.power();
|
||||
cpu.power(reset);
|
||||
apu.power(reset);
|
||||
ppu.power(reset);
|
||||
scheduler.primary(cpu);
|
||||
|
||||
controllerPort1.power(ID::Port::Controller1);
|
||||
|
@@ -11,15 +11,11 @@ struct System {
|
||||
auto load(Emulator::Interface*) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto term() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
@@ -1,6 +0,0 @@
|
||||
auto System::configureVideoPalette() -> void {
|
||||
Emulator::video.setPalette();
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
}
|
@@ -1,13 +1,13 @@
|
||||
processors += lr35902
|
||||
processors += sm83
|
||||
|
||||
objects += gb-interface gb-system
|
||||
objects += gb-memory gb-cartridge
|
||||
objects += gb-cpu gb-ppu gb-apu
|
||||
|
||||
obj/gb-interface.o: gb/interface/interface.cpp $(call rwildcard,gb/interface/)
|
||||
obj/gb-system.o: gb/system/system.cpp $(call rwildcard,gb/system/)
|
||||
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp $(call rwildcard,gb/cartridge/)
|
||||
obj/gb-memory.o: gb/memory/memory.cpp $(call rwildcard,gb/memory/)
|
||||
obj/gb-cpu.o: gb/cpu/cpu.cpp $(call rwildcard,gb/cpu/)
|
||||
obj/gb-ppu.o: gb/ppu/ppu.cpp $(call rwildcard,gb/ppu/)
|
||||
obj/gb-apu.o: gb/apu/apu.cpp $(call rwildcard,gb/apu/)
|
||||
obj/gb-interface.o: gb/interface/interface.cpp
|
||||
obj/gb-system.o: gb/system/system.cpp
|
||||
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp
|
||||
obj/gb-memory.o: gb/memory/memory.cpp
|
||||
obj/gb-cpu.o: gb/cpu/cpu.cpp
|
||||
obj/gb-ppu.o: gb/ppu/ppu.cpp
|
||||
obj/gb-apu.o: gb/apu/apu.cpp
|
||||
|
@@ -55,8 +55,8 @@ auto APU::power() -> void {
|
||||
create(Enter, 2 * 1024 * 1024);
|
||||
if(!Model::SuperGameBoy()) {
|
||||
stream = Emulator::audio.createStream(2, frequency());
|
||||
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
|
||||
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
|
||||
stream->addHighPassFilter(20.0, Emulator::Filter::Order::First);
|
||||
stream->addDCRemovalFilter();
|
||||
}
|
||||
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
||||
|
||||
@@ -68,8 +68,8 @@ auto APU::power() -> void {
|
||||
phase = 0;
|
||||
cycle = 0;
|
||||
|
||||
LinearFeedbackShiftRegisterGenerator r;
|
||||
for(auto& n : wave.pattern) n = r();
|
||||
PRNG::PCG prng;
|
||||
for(auto& n : wave.pattern) n = prng.random();
|
||||
}
|
||||
|
||||
auto APU::readIO(uint16 addr) -> uint8 {
|
||||
|
@@ -17,24 +17,43 @@ Cartridge cartridge;
|
||||
#include "tama/tama.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto Cartridge::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cartridge.main();
|
||||
}
|
||||
|
||||
auto Cartridge::main() -> void {
|
||||
mapper->main();
|
||||
}
|
||||
|
||||
auto Cartridge::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
information = {};
|
||||
rom = {};
|
||||
ram = {};
|
||||
rtc = {};
|
||||
mapper = &mbc0;
|
||||
accelerometer = false;
|
||||
rumble = false;
|
||||
|
||||
if(Model::GameBoy()) {
|
||||
if(auto loaded = platform->load(ID::GameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = loaded.pathID();
|
||||
information.pathID = loaded.pathID;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
if(Model::GameBoyColor()) {
|
||||
if(auto loaded = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
|
||||
information.pathID = loaded.pathID();
|
||||
information.pathID = loaded.pathID;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
if(Model::SuperGameBoy()) {
|
||||
if(auto loaded = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = loaded.pathID();
|
||||
information.pathID = loaded.pathID;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
@@ -43,10 +62,9 @@ auto Cartridge::load() -> bool {
|
||||
} else return false;
|
||||
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
auto board = document["board"];
|
||||
information.title = document["information/title"].text();
|
||||
information.title = document["game/label"].text();
|
||||
|
||||
auto mapperID = document["board/mapper"].text();
|
||||
auto mapperID = document["game/board"].text();
|
||||
if(mapperID == "MBC0" ) mapper = &mbc0;
|
||||
if(mapperID == "MBC1" ) mapper = &mbc1;
|
||||
if(mapperID == "MBC1M") mapper = &mbc1m;
|
||||
@@ -59,53 +77,63 @@ auto Cartridge::load() -> bool {
|
||||
if(mapperID == "HuC1" ) mapper = &huc1;
|
||||
if(mapperID == "HuC3" ) mapper = &huc3;
|
||||
if(mapperID == "TAMA" ) mapper = &tama;
|
||||
if(!mapper) mapper = &mbc0;
|
||||
|
||||
accelerometer = (bool)document["board/accelerometer"];
|
||||
rumble = (bool)document["board/rumble"];
|
||||
accelerometer = (bool)document["game/board/accelerometer"];
|
||||
rumble = (bool)document["game/board/rumble"];
|
||||
|
||||
rom.size = max(0x4000, document["board/rom/size"].natural());
|
||||
rom.data = (uint8*)memory::allocate(rom.size, 0xff);
|
||||
if(auto name = document["board/rom/name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Program)"]}) {
|
||||
rom.size = max(0x4000, (uint)memory.size);
|
||||
rom.data = memory::allocate<uint8>(rom.size, 0xff);
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Required)) {
|
||||
fp->read(rom.data, min(rom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
|
||||
ram.size = document["board/ram/size"].natural();
|
||||
ram.data = (uint8*)memory::allocate(ram.size, 0xff);
|
||||
if(auto name = document["board/ram/name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Optional)) {
|
||||
fp->read(ram.data, min(ram.size, fp->size()));
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
|
||||
ram.size = memory.size;
|
||||
ram.data = memory::allocate<uint8>(ram.size, 0xff);
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Optional)) {
|
||||
fp->read(ram.data, min(ram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rtc.size = document["board/rtc/size"].natural();
|
||||
rtc.data = (uint8*)memory::allocate(rtc.size, 0xff);
|
||||
if(auto name = document["board/rtc/name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Optional)) {
|
||||
fp->read(rtc.data, min(rtc.size, fp->size()));
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RTC,content=Time)"]}) {
|
||||
rtc.size = memory.size;
|
||||
rtc.data = memory::allocate<uint8>(rtc.size, 0xff);
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Optional)) {
|
||||
fp->read(rtc.data, min(rtc.size, fp->size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
information.sha256 = Hash::SHA256(rom.data, rom.size).digest();
|
||||
information.sha256 = Hash::SHA256({rom.data, rom.size}).digest();
|
||||
mapper->load(document);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(auto name = document["board/ram/name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Write)) {
|
||||
fp->write(ram.data, ram.size);
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Write)) {
|
||||
fp->write(ram.data, ram.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(auto name = document["board/rtc/name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Write)) {
|
||||
fp->write(rtc.data, rtc.size);
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RTC,content=Time)"]}) {
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Write)) {
|
||||
fp->write(rtc.data, rtc.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mapper->save(document);
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
@@ -142,6 +170,8 @@ auto Cartridge::writeIO(uint16 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
|
||||
for(uint n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this;
|
||||
for(uint n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this;
|
||||
bus.mmio[0xff50] = this;
|
||||
@@ -167,4 +197,10 @@ auto Cartridge::Memory::write(uint address, uint8 byte) -> void {
|
||||
data[address] = byte;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto Cartridge::Mapper::main() -> void {
|
||||
cartridge.step(cartridge.frequency());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
struct Cartridge : MMIO {
|
||||
struct Cartridge : Thread, MMIO {
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto sha256() const -> string { return information.sha256; }
|
||||
auto hash() const -> string { return information.sha256; }
|
||||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
@@ -11,6 +12,8 @@ struct Cartridge : MMIO {
|
||||
auto readIO(uint16 address) -> uint8;
|
||||
auto writeIO(uint16 address, uint8 data) -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
auto power() -> void;
|
||||
auto second() -> void;
|
||||
|
||||
@@ -35,6 +38,9 @@ struct Cartridge : MMIO {
|
||||
|
||||
private:
|
||||
struct Mapper {
|
||||
virtual auto load(Markup::Node document) -> void {}
|
||||
virtual auto save(Markup::Node document) -> void {}
|
||||
virtual auto main() -> void;
|
||||
virtual auto second() -> void {}
|
||||
virtual auto read(uint16 address) -> uint8 = 0;
|
||||
virtual auto write(uint16 address, uint8 data) -> void = 0;
|
||||
|
@@ -32,7 +32,7 @@ auto Cartridge::MBC5::write(uint16 address, uint8 data) -> void {
|
||||
}
|
||||
|
||||
if((address & 0xe000) == 0x4000) { //$4000-5fff
|
||||
if(cartridge.rumble) platform->inputRumble(ID::Port::Hardware, ID::Device::Controls, 10, data.bit(3));
|
||||
if(cartridge.rumble) platform->inputRumble(ID::Port::Cartridge, ID::Device::MBC5, 0, data.bit(3));
|
||||
io.ram.bank = data.bits(0,3);
|
||||
return;
|
||||
}
|
||||
|
239
higan/gb/cartridge/mbc7/eeprom.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
//Microchip 93LCx6
|
||||
// 93LC46 => 1024 cells => 128 x 8-bit or 64 x 16-bit
|
||||
// 93LC56 => 2048 cells => 256 x 8-bit or 128 x 16-bit
|
||||
// 93LC66 => 4096 cells => 512 x 8-bit or 256 x 16-bit
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::load(Markup::Node document) -> void {
|
||||
for(auto& byte : data) byte = 0xff;
|
||||
size = 512; //EEPROM size is in bytes
|
||||
width = 16; //16-bit configuration
|
||||
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=EEPROM,content=Save)"]}) {
|
||||
if(memory.size == 128) size = 128;
|
||||
if(memory.size == 256) size = 256;
|
||||
if(memory.size == 512) size = 512;
|
||||
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read, File::Optional)) {
|
||||
fp->read(data, min(fp->size(), sizeof(data)));
|
||||
}
|
||||
}
|
||||
|
||||
//note: the 93LC56 alone has an extra dummy address bit
|
||||
if(size == 128) input.addressLength = width == 16 ? 6 : 7; //93LC46
|
||||
if(size == 256) input.addressLength = width == 16 ? 8 : 9; //93LC56
|
||||
if(size == 512) input.addressLength = width == 16 ? 8 : 9; //93LC66
|
||||
input.dataLength = width;
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::save(Markup::Node document) -> void {
|
||||
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=EEPROM,content=Save)"]}) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Write)) {
|
||||
fp->write(data, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::main() -> void {
|
||||
//step by approximately one millisecond
|
||||
cartridge.step(cartridge.frequency() / 1000);
|
||||
|
||||
//set during programming commands
|
||||
if(busy) busy--;
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::power() -> void {
|
||||
select = 0;
|
||||
clock = 0;
|
||||
writable = 0;
|
||||
busy = 0;
|
||||
|
||||
input.flush();
|
||||
output.flush();
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::readIO() -> uint8 {
|
||||
uint8 data = 0b00'1111'00;
|
||||
data.bit(7) = select;
|
||||
data.bit(6) = clock;
|
||||
data.bit(1) = input.edge();
|
||||
if(!select) {
|
||||
data.bit(0) = 1; //high-z when the chip is idle (not selected)
|
||||
} else if(busy) {
|
||||
data.bit(0) = 0; //low when a programming command is in progress
|
||||
} else if(output.count) {
|
||||
data.bit(0) = output.edge(); //shift register data during read commands
|
||||
} else {
|
||||
data.bit(0) = 1; //high-z during all other commands
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::writeIO(uint8 data) -> void {
|
||||
//chip enters idle state on falling CS edge
|
||||
if(select && !data.bit(7)) return power();
|
||||
|
||||
//chip leaves idle state on rising CS edge
|
||||
if(!(select = data.bit(7))) return;
|
||||
|
||||
//input shift register clocks on rising edge
|
||||
if(!clock.raise(data.bit(6))) return;
|
||||
|
||||
//read mode
|
||||
if(output.count && !data.bit(1)) {
|
||||
if(input.start() && *input.start() == 1) {
|
||||
if(input.opcode() && *input.opcode() == 0b10) {
|
||||
output.read();
|
||||
if(output.count == 0) {
|
||||
//sequential read mode
|
||||
input.increment();
|
||||
read();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
output.flush();
|
||||
|
||||
input.write(data.bit(1));
|
||||
|
||||
//wait for start
|
||||
if(!input.start()) return;
|
||||
uint start = *input.start();
|
||||
|
||||
//start bit must be set
|
||||
if(start != 1) return input.flush();
|
||||
|
||||
//wait for opcode
|
||||
if(!input.opcode()) return;
|
||||
uint opcode = *input.opcode();
|
||||
|
||||
//wait for address
|
||||
if(!input.address()) return;
|
||||
|
||||
if(opcode == 0b00) {
|
||||
auto mode = *input.mode();
|
||||
if(mode == 0b00) return writeDisable();
|
||||
if(mode == 0b01) return writeAll();
|
||||
if(mode == 0b10) return eraseAll();
|
||||
if(mode == 0b11) return writeEnable();
|
||||
}
|
||||
if(opcode == 0b01) return write();
|
||||
if(opcode == 0b10) return read();
|
||||
if(opcode == 0b11) return erase();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::read() -> void {
|
||||
uint address = *input.address() << (width == 16) & size - 1;
|
||||
output.flush();
|
||||
for(uint4 index : range(width)) {
|
||||
output.write(data[address + !index.bit(3)].bit(index.bits(0,2)));
|
||||
}
|
||||
output.write(0); //reads have an extra dummy data bit
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::write() -> void {
|
||||
if(!input.data()) return; //wait for data
|
||||
if(!writable) return input.flush();
|
||||
uint address = *input.address() << (width == 16) & size - 1;
|
||||
for(uint4 index : range(width)) {
|
||||
data[address + !index.bit(3)].bit(index.bits(0,2)) = input.read();
|
||||
}
|
||||
busy = 4; //milliseconds
|
||||
return input.flush();
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::erase() -> void {
|
||||
if(!writable) return input.flush();
|
||||
uint address = *input.address() << (width == 16) & size - 1;
|
||||
for(uint index : range(width)) {
|
||||
data[address + index / 8].bit(index % 8) = 1;
|
||||
}
|
||||
busy = 4; //milliseconds
|
||||
return input.flush();
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::writeAll() -> void {
|
||||
if(!input.data()) return; //wait for data
|
||||
if(!writable) return input.flush();
|
||||
auto word = *input.data();
|
||||
for(uint address = 0; address < 512;) {
|
||||
data[address++] = word.byte(width == 16);
|
||||
data[address++] = word.byte(0);
|
||||
}
|
||||
busy = 16; //milliseconds
|
||||
return input.flush();
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::eraseAll() -> void {
|
||||
if(!writable) return input.flush();
|
||||
for(auto& byte : data) byte = 0xff;
|
||||
busy = 8; //milliseconds
|
||||
return input.flush();
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::writeEnable() -> void {
|
||||
writable = true;
|
||||
return input.flush();
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::writeDisable() -> void {
|
||||
writable = false;
|
||||
return input.flush();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::ShiftRegister::flush() -> void {
|
||||
value = 0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::ShiftRegister::edge() -> uint1 {
|
||||
return value.bit(0);
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::ShiftRegister::read() -> uint1 {
|
||||
uint1 bit = value.bit(0);
|
||||
value >>= 1;
|
||||
count--;
|
||||
return bit;
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::ShiftRegister::write(uint1 bit) -> void {
|
||||
value <<= 1;
|
||||
value.bit(0) = bit;
|
||||
count++;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::InputShiftRegister::start() -> maybe<uint1> {
|
||||
if(count < 1) return {};
|
||||
return {value >> count - 1 & 1};
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::InputShiftRegister::opcode() -> maybe<uint2> {
|
||||
if(count < 1 + 2) return {};
|
||||
return {value >> count - 3 & 3};
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::InputShiftRegister::mode() -> maybe<uint2> {
|
||||
if(count < 1 + 2 + addressLength) return {};
|
||||
return {value >> count - 5 & 3};
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::InputShiftRegister::address() -> maybe<uint9> {
|
||||
if(count < 1 + 2 + addressLength) return {};
|
||||
return {value >> count - (3 + addressLength) & (1 << addressLength) - 1};
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::InputShiftRegister::data() -> maybe<uint16> {
|
||||
if(count < 1 + 2 + addressLength + dataLength) return {};
|
||||
return {value >> count - (3 + addressLength + dataLength) & (1 << dataLength) - 1};
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::InputShiftRegister::increment() -> void {
|
||||
value.bits(0, addressLength - 1)++;
|
||||
}
|