Compare commits

...

85 Commits
v081 ... v087

Author SHA1 Message Date
Tim Allen
386ac87d21 Update to v087 release.
byuu says:

This release adds ST018 emulation. As this was the final unsupported
SNES coprocessor, this means that bsnes v087 is the first SNES emulator
to be able to claim 100% known compatibility with all officially
released games. And it does this with absolutely no hacks.

Again, I really have to stress the word known. No emulator is perfect.
No emulator ever really can be perfect for a system of this complexity.
The concept doesn't even really exist, since every SNES behaves subtly
different. What I mean by this, is that every single game ever
officially sold has been tested, and zero bugs (of any severity level)
are currently known.

It is of course extremely likely that bugs will be found in this
release, as well as in future releases. But this will always be
a problem for every emulator ever made: there is no way to test every
possible codepath of every single game to guarantee perfection. I will,
of course, continue to do my best to fix newfound bugs so long as I'm
around.

I'd really like to thank Cydrak and LostTemplar for their assistance in
emulating the ST018. I could not have done it without their help.

The ST018 ROM, like the other coprocessor ROMs, is copyrighted. This
means I am unable to distribute the image.

Changelog (since v086):
- emulated the 21.47MHz ST018 (ARMv3) coprocessor used by Hayazashi
  Nidan Morita Shougi 2
- fixed PPU TM/TS edge case; fixes bottom scanline of text boxes in
  Moryo Senki Madara 2
- fixed saving and loading of Super Game Boy save RAM
- NEC uPD7725,96050 ROMs now stored in little-endian format for
  consistency
- cartridge folder concept has been reworked to use fixed file names
- added emulation of serial USART interface (replaces asynchronous UART
  support previously)
2012-03-08 00:29:38 +11:00
Tim Allen
533aa97011 Update to v086r16 release.
byuu says:

Cydrak, I moved the step from the opcode decoder and opcodes themselves
into bus_(read,write)(byte,word), to minimize code.
If that's not feasible for some reason, please let me know and I'll
change it back to your latest WIP.
This has your carry flag fix, the timer skeleton (doesn't really work
yet), the Booth two-bit steps, and the carry flag clear thing inside
multiply ops.
Also added the aforementioned reset delay and reset bit stuff, and fixed
the steps to 21MHz for instructions and 64KHz for reset pulse.
I wasn't sure about the shifter extra cycles. I only saw it inside one
of the four (or was it three?) opcodes that have shifter functions.
Shouldn't it be in all of them?

The game does indeed appear to be fully playable now, but the AI doesn't
exactly match my real cartridge.
This could be for any number of reasons: ARM CPU bug, timer behavior
bug, oscillator differences between my real hardware and the emulator,
etc.
However ... the AI is 100% predictable every time, both under emulation
and on real hardware.

- For the first step, move 九-1 to 八-1.
- The opponent moves 三-3 to 四-3.
- Now move 七-1 to 六-1.
- The opponent moves 二-2 to 八-8.
  However, on my real SNES, the opponent moves 一-3 to 二-4.
2012-03-08 00:03:15 +11:00
Tim Allen
d118b70b30 Update to v086r15 release.
byuu says:

Most importantly ... I'm now using "st018.rom" which is the program ROM
+ data ROM in one "firmware" file. Since all three Seta DSPs have the
ST01N stamp, unlike some of the arcade variants, I'm just going to go
with ST01N from now on instead of ST-001N. I was using the latter as
that's what Overload called them.

Moving on ...
The memory map should match real hardware now, and I even match the open
bus read results.
I also return the funky 0x40404001 for 60000000-7fffffff, for whatever
that's worth.
The CPU-side registers are also mirrored correctly, as they were in the
last WIP, so we should be good there.
I also simulate the reset pulse now, and a 0->!0 transition of $3804
will destroy the ARM CPU thread.
It will wait until the value is set back to zero to resume execution.
At startup, the ARM CPU will sleep for a while, thus simulating the
reset delay behavior.
Still need to figure out the exact cycle length, but that's really not
important for emulation.

Note in registers.hpp, the |4 in status() is basically what allows the
CPU program to keep going, and hit the checkmate condition.
If we remove that, the CPU deadlocks. Still need to figure out how and
when d4 is set on $3804 reads.
I can run any test program on both real hardware and in my emulator and
compare results, so by all means ... if you can come up with a test,
I'll run it.
2012-03-02 22:07:17 +11:00
Tim Allen
aa8ac7bbb8 Update to v086r14 release.
byuu says:

Attempted to fix the bugs pointed out by Cydrak for the shifter carry
and subtraction flags. No way to know if I was successful.
The memory map should exactly match real hardware now.
Also simplified bus reading/writing: we can get fancy when it works,
I suppose.
Reduced some of the code repetition to try and minimize the chances for
bugs.
I hopefully fixed up register-based ror shifting to what the docs were
saying.
And lastly, the disassembler should handle every opcode in every mode
now.
ldr rn,[pc,n] adds (pc,n) [absolute address] after opcode. I didn't want
to actually read from ROM here (in case it ever touches I/O or
something), but I suppose we could try anyway.
At startup, it will write out "disassembly.txt" which is a disassembly
of the entire program ROM.
If anyone wants to look for disassembly errors, I'll go ahead and fix
them. Just note that I won't do common substitutions like mov pc,lr ==
ret.

At this point, we can make two moves and then the game tells us that
we've won.
So ... I'm back to thinking the problem is with bugs in the ARM core,
and that our bidirectional communication is strong enough to play the
game.
Although that's not perfect. The game definitely looks at d4 (and
possibly others later), but my hardware tests can't get anything but
d0/d3 set.
2012-03-01 23:23:05 +11:00
Tim Allen
ad71e18e02 Update to v086r13 release.
byuu says:

That's my best implementation of the shifter carry. It's horribly
inefficient and possibly wrong (especially on ROR by register, but that
doesn't ever appear to be used in this program), but oh well. It's the
best I can do.

Game is basically getting stuck after a board upload and issuing another
command. It's sitting in a loop waiting on $3804.d0 to be set, meaning
the ARM is never writing anything for the CPU to read. There's some
chance that my $3804/r40000000 flags are wrong. Short of guessing
though, I'm not sure how we can get more info on how those work.

... I really can't debug this any better than I have. If no one else
sees anything, then we're going to have to give up and wait for MESS to
create opcode logs for us to compare against.
2012-02-29 23:59:48 +11:00
Tim Allen
a00c7cb639 Update to v086r12 release.
byuu says:

Attract demonstration game is fully playable.
2012-02-29 23:56:21 +11:00
Tim Allen
112520cf45 Update to v086r11 release.
byuu says:

More ARM work. Can get in-game, and upload the board (0xaa) successfully.
Bug in checkmate command makes the CPU really difficult to defeat :P
2012-02-28 22:21:18 +11:00
Tim Allen
11d6f09359 Update to v086r10 release.
byuu says:

More ARM work. ARM core now begins to act upon initial 0xf1 command, but
hangs.
2012-02-28 22:10:02 +11:00
Tim Allen
3ed42af8a1 Update to v086r09 release.
byuu says:

A lot more work on the ARMv3 core.
2012-02-27 11:18:50 +11:00
Tim Allen
482b4119f6 Update to v086r08 release.
byuu says:

Contains the fledgling beginnings of an ARM CPU core, which can execute
the first three and a half instructions of the ST-0018.
It's a start, I guess.
2012-02-26 18:59:44 +11:00
Tim Allen
f1d6325bcd Update to v086r07 release.
byuu says:

USART improvements. The clock pulse from reading data() drives both
reading and writing.
Also added a usart_init() to bind the initializer functions, so all you
need now is:
extern "C" usartproc void usart_main() { ... }
And inside, you use usart_read(), usart_write(), etc.
So we can add all the new functions we want (eg I'd like to have
usart_readable() to check if data is available) without changing the
entry point signature.

blargg enhanced his Teensy driver to ignore frame error reads, as well.
2012-02-25 20:12:08 +11:00
Tim Allen
e48671694e Update to v086r06 release.
byuu says:

It is done. bsnes can now emulate sending and receiving data via USART.
As such, the UART code has been removed.
The final UART code can be downloaded here: http://byuu.org/snes/uart/
I won't maintain it going forward, because nobody ever used it, and
USART is superior in every way.

I've also verified both sending and receiving on the real SNES now :D
It's so easy ... a caveman with electrical engineering and computer
programming experience can do it.
2012-02-25 20:03:11 +11:00
Tim Allen
338f13e57b Update to v086r05 release.
byuu says:

USART implements reading and writing, but I don't yet have code to test
SNES reading yet.
So ... obviously I need to do that next.

Went ahead and required nall::function, so the modules will have to be
C++11. I don't see anyone else making these, and it avoids the annoyance
of deducing the correct controller port based on dynamic casting the
active thread.
Apparently a library can have a main() function to no ill effect, so
there's no need for USART_HARDWARE. Same exact code with different flags
will make the binary and the library.
2012-02-25 19:52:42 +11:00
Tim Allen
6cbc312f11 Update to v086r04 release.
byuu says:

There will probably be a series of small WIPs as I experiment here.

snes/controller/serial is now snes/controller/uart. Asynchronous serial
communications, typically capped at 57,600 baud.
snes/controller/usart is new. It aims to emulate the SNES connected to
a Teensy++ board, and can easily handle 524,288 baud.
And much more importantly, it's synchronous, so there are no timing
issues anymore. Just bit-bang as fast as you can.

Right now, the USART code is just enough for SNES->PC to transfer data
to ... well, nothing yet.

Unless anyone is actually using the UART stuff, I'll be removing it once
the USART is totally up and running.
No sense maintaining code that is 10x slower, more error prone, and used
by nobody.

Note: this is all thanks to blargg being absolutely amazing.
2012-02-25 19:49:27 +11:00
Tim Allen
7a96321e78 Update to v086r03 release.
byuu says:

Cart unload save path was using the new game rather than the old game.
Caused by trying to allow a failed cartridge load to not unload the
current game.
But that's so uncommon that it's not worth worrying about. It'll always
unload before trying to load a new game now.

Removed the TM/TS disable speedup, to fix Madara 2's text boxes.
This actually did cause a slight performance penalty on games that
disable layers via TM/TS. Zelda 3 inside Link's house is a good example.
It knocked the FPS from 98.5 to 94.5. So to counter that, I removed
conditionals from tiledata loading and decoding, and used fall through
switches.
This boosted us back to 97.0. The -march=native flag apparently works
better with SB now, so that was added, putting us up to 99.0fps.
So it should be the same speed in the worst case, and slightly faster in
the best case.

Bumped the pre-render time to 68 clocks from 60 clocks. Adjusted sprite
tile fetch time from 22 to 14 to compensate.
This should give us perfectly stable Dai Kaijuu Monogatari 2 battles.
2012-02-18 18:49:52 +11:00
Tim Allen
6cfb9e89e7 Update to v086r02 release.
byuu says:

Fixed Super Game Boy RAM saving and loading. It plainly wasn't hooked up
at all. Was apparently hard-coded before it became a multi-emulator.
I also fixed a crashing issue when loading Satellaview-slotted or
Satellaview games without specifying the sub-cart, wasn't setting
has_bsx_slot = true, so the raw memory wasn't being allocated internally
when it wasn't mapped in. Of course a better fix would be to just not
physically map the ranges if the things aren't present. Kind of a lazy
hack to map blank cartridges there, but oh well.  Oh, fixed title
displays as well; and did the best I could for now with regards to
multi-file path saving.
2012-02-16 23:48:05 +11:00
Tim Allen
a37ce1cb2f Update to v086r01 release.
byuu says:

The goals for v087 are to have a unified cartridge-folder concept, as
well as a more functional SNES debugger.

Starting with the cartridge folders. What I have so far:

Code:
NES:
- program.rom
- character.rom
- program.ram
- …

SNES:
- program.rom
- program.rtc
- data.rom (SPC7110)
- { dsp1.rom, dsp1b.rom, cx4.rom, … }
- msu1.rom
- track-#.pcm

Game Boy, Game Boy Color:
- program.rom
- program.ram
- program.rtc

Sub-cartridges (BS-X, Sufami Turbo, …) are stored as separate folders
Folder names must be UTF-8 based, with all-lowercase extensions
File names must be all-lowercase

SNES:
- "program.ram" (.srm, .sts)
- "msu1.rom" (name.msu)
- "track-#.pcm" (name-#.pcm)
- "upd96050.ram" -> "name.ram"
- "bsx.ram" (.bss)
- "bsx.psram" (.bsp)
- "serial.so" -> "libserial.so" (broken)

Need:
- Super Game Boy (not even sure how this loads and saves memory, it's
  obviously broken)

And I need to think of some way of handling multi-cart loaded games.
Eg Satellaview-slotted and Sufami Turbo. It was { base + slot ( + slot
... } }, but this gets trickier with folders and fixed names.
Actual markup for the NES needs to change as well.
2012-02-16 01:01:22 +11:00
Tim Allen
10fd29e7bb Update to v086 release.
byuu says:

The main focus of this release is Laevateinn, which is the new bsnes
debugger. Unlike previous debuggers, Laevateinn is a standalone
application with its own GUI entirely focused on debugging.

Changelog:
- created ui-debugger target (Laevateinn)
- fixed multitap ports 2-4 [quequotion]
- fixed ui-libsnes target compilation
- fixed a crashing issue with NSS XML markup
- improved cartridge-folder loading support
- NES can now load .fc (headerless NES) or .prg+.chr (split NES) images
- fixed cursor being visible in fullscreen mode when using
  Linux/Metacity window manager [ncbncb]
- show normal cursor when using Linux/SDL video driver [ncbncb]
- added menu accelerators
- fixed a bug in performance profile SMP incw/decw instructions
- SNES core can now optionally be built without Game Boy emulation core
- added 2012-02-04 cheats.xml database [mightymo]
2012-02-13 22:44:02 +11:00
Tim Allen
0370229444 Update to v085r09 release.
byuu says:

Added VRAM viewer (mouse over to get tile# and VRAM address), CPU+SMP
register editors, settings.cfg to cache path+sync audio+mute audio
settings (Windows Vista+ ignore my request for the default folder
because they are fucking stupid, so they always default to your home
folder. I'm going to have to recommend using a batch file to start
laevateinn there. Sorry, blame Microsoft for being fuck-ups),
geometry.cfg to remember where you placed windows and what size you made
them (a bug in Qt prevents me from making some windows fixed-size for
now, but that'll change when I can work around the Qt issue), usage map
invalidation if the ROM was modified after the usage files, that empty
line insertion thing creaothceann wanted on emulation resume, all chips
now synchronize immediately rather than just-in-time, which is important
for a debugger.

Going to postpone the properties viewer until after v086.

So this is pretty much ready for release. Please bug-test. I don't care
so much about little frills like "oh the memory editor window should
default to a little bigger", you can work around that by resizing it.
I care about things like, "VRAM write breakpoints don't work at all."

If we miss any bugs and it gets released, not the end of the world, but
you'll be waiting a while for the next release to address any missed
bugs now.
2012-02-12 20:58:04 +11:00
Tim Allen
82afd511fc Update to v085r08 release.
byuu says:

Changelog:
- follow the Laevateinn topic to get most of it
- also added NMI, IRQ step buttons to CPU debugger
- also added trace masking + trace mask reset
- also added memory export
- cartridge loading is entirely folder-based now

FitzRoy, I'll go ahead and make a second compromise with you for v086:
I'll match the following:

    /path/to/SNES.sfc/*.sfc
    /path/to/NES.fc/*.prg, *.chr (split format)
    /path/to/NES.fc/*.fc (merged format)
    /path/to/GB.gb/*.gb
    /path/to/GBC.gbc/*.gbc

Condition will be that there can only be one of each file. If there's
more than one, it'll abort. That lets me name my ROMs as
"Game.fc/Game.fc", and you can name yours as "Game.fc/cartridge.prg,
cartridge.chr". Or whatever you want.
We'll just go with that, see what fares out as the most popular, and
then restrict it back to that method.
The folder must have the .fc, etc extension though. That will be how we
avoid false-positive folder matches.

[Editor's note - the Laevateinn topic mentions these changes for
v085r08:

    Added SMP/PPU breakpoints, SMP debugger, SMP stepping / tracing,
    memory editing on APU-bus / VRAM / OAM / CGRAM, save state menu,
    WRAM mirroring on breakpoints, protected MMIO memory regions
    (otherwise, viewing $002100 could crash your game.)

    Major missing components:
    - trace mask
    - trace mask clear / usage map clear
    - window geometry caching / sizing improvements
    - VRAM viewer
    - properties viewer
    - working memory export button

    The rest will most likely appear after v086 is released.
]
2012-02-12 16:35:40 +11:00
Tim Allen
ad3eafd735 Update to v085r07 release.
byuu says:

Changelog:
- stuff

[Editor's note - pretty much just more debugger implementation]
2012-02-12 16:07:24 +11:00
Tim Allen
4bc5f66aa5 Update to v085r06 release.
byuu says:

Lots of debugger enhancements. Memory editor works for CPU-bus only,
breakpoint editor does nothing yet.
Tracing works, writes to 001-999 files sequentially. Stepping works,
too. But only on the CPU.
Added "privileged", which becomes "public" if DEBUGGER is defined,
"private" otherwise.
Meant so the debugger can stab deeply into the cores for state
manipulation. Interface is guaranteed to be unstable and dependent upon
the accuracy core.
The about screen logo adds 100KB onto the source download (won't affect
regular bsnes binaries), but too bad. I want some visual flair this
time.
2012-02-09 23:53:55 +11:00
Tim Allen
730e6ae4cc Update to v085r04 release.
byuu says:

Changelog:
- added base/ folder
- base/base.hpp defines the version number for all UI targets, all the
  varint-types, and a hook() class for debugger functions (see below)
- fixed compatibility profile compilation
- removed within<> template from the SNES target
- the SNES core can be built without Game Boy support now, if you so
  choose (my SNES debugger is not going to support debugging the GBZ80,
  sorry.)
- added ui-debugger; not at all useful right now, will be a long while
  to get something usable ready

So hook is a class wrapper around nall::function. It allows you to
invoke potentially empty functions (and as such, the return type must
have a trivial constructor.)
It also doesn't actually perform the test+invocation when DEBUGGER
(options=debugger) is not defined. So you should have no overhead in
regular builds.
The core classes now have a subclass with all the debugging hooks, so
you'll see eg:

    void CPU::op_step() {
      debugger.op_exec(regs.pc);
      (this->*opcode_table[op_read()])();
    }

Clear what it's doing, clear what it's for. A whole lot less work than
inheriting the whole CPU core and virtualizing the functions we want to
hook.
All the logic for what to do inside these callbacks will be handled by
individual debuggers, so they can have all the functionality they want.
2012-02-06 23:03:45 +11:00
Tim Allen
892bb3ab01 Update to v085r03 release.
byuu says:

Changelog:
- fixed cursor being visible under Metacity window manager (hopefully
  doesn't cause regression with other WMs)
- show normal cursor when using SDL video driver
- added menu accelerators (meh, why not?)
- removed debugvirtual, ChipDebugger and chip/debugger functionality
  entirely
- alt/smp disassembler moved up
- fixed alt/smp incw/decw instructions (unsigned->uint16 for internal
  variables)

My plan going forward for a debugger is not to hardcode functionality
that causes the 10-15% slowdown right into the emulator itself.
Instead, I'm going to make a callback class, which will be a specialized
version of nall::function:
- can call function even if not assigned (results in no-op, return type
  must have a trivial default constructor)
- if compiled without #define DEBUGGER, the entire thing turns into
  a huge no-op; and will be eliminated entirely when compiled
- strategically place the functions: cb_step, cb_read, cb_write, etc.

From here, the ui-debugger GUI will bind the callbacks, implement
breakpoint checking, usage table generation, etc itself.
I'll probably have to add some breakout commands to exit the emulation
core prior to a frame event in some cases as well.

I didn't initially want any debugger-related stuff in the base cores,
but the #if debugger sCPUDebugger #else sCPU #endif stuff was already
more of a burden than this will be.
2012-02-04 20:23:53 +11:00
Tim Allen
e4e50308d2 Update to v085r02 release.
byuu says:

Fixed NSS XML crashing issue.
Improved folder-loading support.
NES can now load game.fc/game.fc, or game.fc/game.prg+game.chr.
Both types should have no iNES header at all.
And both types require an XML file (until we have a built-in database.)
2012-01-26 17:50:09 +11:00
Tim Allen
cc518dcc3c Update to v085r01 release.
byuu says:

Changelog:
- updated bsnes to use the newest versions of nall and phoenix
- fixed ui-libsnes compilation (testing would be a good idea, especially
  the cheat codes. I just copy-pasted that from the regular UI.)
- fixed multitap controllers 2-4 [quequotion]
2012-01-15 19:29:57 +11:00
Tim Allen
ba081d309e Update to v085 release.
byuu says:

A new release for the new year.

Changelog:
fixed auto joypad polling edge case; fixes Ys 5 controls
fixed Justifier polling code; Lethal Enforcers should be fully
responsive once again
rewrote SNES S-SMP processor core (~20% code reduction)
fixed Game Boy 8x16 sprite mode; fixed some sprites in Zelda: Link's
Awakening
treat Game Boy HuC1 RAM enable flag as writable flag instead; fixes
Pokemon Card GB
created far faster XML parser; bsnes can now load XML files once again
updated to mightymo's most recent cheat code database
internal color calculations now performed at 30-bits per pixel
gamma slider now acts as fine-tuned gamma ramp option
Linux OpenGL driver will output at 30bpp on capable displays
Linux port defaults to GTK+ now instead of Qt (both are still available)
2012-01-04 00:10:46 +11:00
Tim Allen
1bf9265b7c Update to v084r08 release.
byuu says:

Okay, everything can now load XML again, including board layouts for all
three systems. New is the ability to load external Game Boy layouts (not
really that useful, but it's there.)
I'd like to aim for a v085 release soon. I've included a binary, so I'd
appreciate testing. I had to redo all of the XML mappings for every
system (I like consistency), so basically the following things need to
be tested:
* load one of every type of game for every system (every NES board type,
* every Game Boy MBC type, every SNES chip and layout type.)
* test cheat codes and the cheat database
* test pixel shaders for OpenGL and Direct3D (sepia for the win)
* test anything else for v085 release
2011-12-31 20:24:58 +11:00
Tim Allen
f947d84309 Update to v084r07 release.
byuu says:

Added the new super-fast XML parser. So far, the shaders, cheat files,
and cheat database have been updated to allow XML mode once again. Which
is sure to please Screwtape =)
So it's down to just the cartridge mapping files now, which are always
a major pain.

I still think BML is better for parsing simplicity, memory usage, disk
size, lack of red tape and speed (but horrendously bad for ease of
creating files manually), but since the base API is identical, there's
no reason not to support both. Especially since the pixel shaders have
kind of taken on a life of their own.
2011-12-30 17:41:29 +11:00
Tim Allen
0bd21185b8 Update to v084r06 release.
byuu says:

Changelog:
- fixed sprite tile masking for 8x16 mode (fixes Zelda: DX sprites)
- HuC1 flag sets RAM writable, not RAM enable (fixes Pokemon Card)
- removed within<> template, didn't turn out to be all that useful

I would be almost certain no games would break by allowing reads when it
is disabled, no game would rely on that behavior.
I prefer to be overly restrictive. Better to not allow valid behavior
than to allow invalid behavior. The latter is what gives us a dozen
broken SNES translations.
2011-12-26 21:49:48 +11:00
Tim Allen
6227974bf6 Update to v084r05 release.
(note: before the post announcing this release, there had been
a discussion of a performance optimisation that made the Super Scope
emulation a lot faster, but caused problems for the Justifier perpheral)

byuu says:

Spent a good two hours trying things to no avail.
I was trying to allow the CPU to run ahead, and sync on accesses to
$4016/4017/4201/4213, but that doesn't work because the controllers have
access to strobe IObit at will.
The codebase is really starting to get difficult to work with. I am
guessing because the days of massive development are long over, and the
code is starting to age.
Jonas' fix works 98% of the time, but there's still a few missed shots
here and there. So that's not going to work either.
So ... I give up. I've disabled the speed hack, so that it works 100% of
the time.
Did the same for the Super Scope: it may not have the same problem, but
I like consistency and don't feel like taking the chance.
This doesn't affect the mouse, since the mouse does not latch the
counters to indicate its X/Y position.
Speed hit is 92->82fps (accuracy profile), but only for Super Scope and
Justifier games.
But ... at least it works now. Slow and working is better than fast and
broken.

I appreciate the help in researching the issue, Jonas and krom.

Also pulled in phoenix/Makefile, which simplifies ui/Makefile.
Linux port defaults to GTK+ now. I can't get QGtkStyle to look good on
Debian.
2011-12-18 14:19:45 +11:00
Tim Allen
ea95eaca3c Update to v084r04 release.
byuu says:

Fixed the Ys 5 input bug in the auto joypad polling code. Can't
guarantee it's hardware-accurate (I have no way to extensively test it),
but I can guarantee it is closer to being correct now.
Also uses updated version of phoenix.

The justifier input is indeed all fucked up now. Seems like it stops
updating input after firing for a few frames.
I really don't want to debug that code anymore ... anyone want to make
$10 by fixing it? :P
2011-12-12 21:59:53 +11:00
Tim Allen
ad0805b168 Update to v084r03 release.
(r02 was not posted to the WIP thread)

byuu says:

Internally, all color is processed with 30-bit precision. The filters
also operate at 30-bit depth.
There's a new config file setting, video.depth, which defaults to 24.
This causes the final output to downsample to 24-bit, as most will
require.
If you set it to 30-bit, the downsampling will not occur, and bsnes will
ask ruby for a 30-bit surface. If you don't have one available, you're
going to get bad colors. Or maybe even a crash with OpenGL.
I don't yet have detection code to make sure you have an appropriate
visual in place.

30-bit mode will really only work if you are running Linux, running Xorg
at Depth 30, use the OpenGL or XShm driver, have an nVidia Quadro or AMD
FireGL card with the official drivers, and have a 30-bit capable
monitor.
Lots of planning and work for very little gain here, but it's nice that
it's finally finished.

Oh, I had to change the contrast/brightness formulas a tiny bit, but
they still work and look nice.
2011-12-03 14:22:54 +11:00
Tim Allen
2cc077e12b Update to v084r01 release.
I rewrote the S-SMP processor core (implementation of the 256 opcodes),
utilizing my new 6502-like syntax. It matches what bass v05r01 uses.
Took 10 hours.
Due to being able to group the "mov reg,mem" opcodes together with
"adc/sbc/ora/and/eor/cmp" sets, the total code size was reduced from
55.7KB to 42.5KB for identical accuracy and speed.
I also dropped the trick I was using to pass register variables as
template arguments, and instead just use a switch table to pass them as
function arguments. Makes the table a lot easier to read.

Passes all of my S-SMP tests, and all of blargg's
arithmetic/cycle-timing S-SMP tests. Runs Zelda 3 great as well. Didn't
test further.
This does have the potential to cause some regressions if I've messed
anything up, and none of the above tests caught it, so as always,
testing would be appreciated.

Anyway, yeah. By writing the actual processor with this new mnemonic
set, it confirms the parallels I've made.
My guess is that Sony really did clone the 6502, but was worried about
legal implications or something and changed the mnemonics last-minute.

(Note to self: need to re-enable snes.random before v085 official.)

EDIT: oh yeah, I also commented out the ALSA snd_pcm_drain() inside
term(). Without it, there is a tiny pop when the driver is
re-initialized. But with it, the entire emulator would lock up for five
whole seconds waiting on that call to complete. I'll take the pop any
day over that.
2011-11-17 23:05:35 +11:00
Tim Allen
ae6c3c377d Update to v084 ninja bug-fix.
byuu says:

Hiding the viewport is necessary on Windows to prevent it from
overlapping the status bar. I've changed it to set the size to 1,1 when
nothing is loaded.
That still puts a 1x1 pixel over the status bar when you resize the
window to 1xHeight, but ... you know, don't do that.
Also corrected the mask overscan option for NES/SNES.

Silently updated the bsnes_v084-source.tar.bz2 archive with those fixes,
there were only 48 downloads.
2011-11-08 22:58:50 +11:00
Tim Allen
01750e9c83 Update to v084 release.
byuu says:

This release adds preliminary Game Boy Color emulation. Due to lack of
technical information, this is undoubtedly the least stable module
I provide at this time; but improvements should continue as it is
developed.

This release also polishes the NES emulation and user interface code.

Changelog (since v083):
- added preliminary Game Boy Color emulation
- NES: added MMC6, VRC1, VRC2, VRC3 emulation
- NES: fixed MMC5 banking and added split-screen support [Cydrak]
- NES: pass all of blargg's ppu_vbl_nmi tests, pass more sprite tests
- NES: palette is now generated algorithmically [Bisqwit]
- SNES: fixed SA-1 IRQ regression caused by code refactoring
- Game Boy: rewrote audio channel mixing code; sound output is greatly
  improved as a result
- Game Boy: uses DMG boot ROM instead of SGB boot ROM
- Game Boy: fixed potential bug when loading save states
- phoenix: fixed ListView focus issue [X-Fi6]
- phoenix: fixed dialog message parsing [X-Fi6]
- ui: video output is truly 24-bit now; SNES luma=0 edge case emulated
- ui: audio frequency, latency, resampler are now user configurable
- ui: gamma ramp is dynamically adjustable
- ui: all filters ported to 24-bit mode (speed hit to HQ2x)
- ui: added turbo button mappings for all generic controllers
- ui: fixed audio volume on unmute via menu [Ver Greeneyes]
- ui: shrink window option does nothing when no cartridge is loaded
- ui: re-added compositor disable, driver verification from v082
2011-11-08 00:04:58 +11:00
Tim Allen
891f1ab7af Update to v083r10 release.
byuu says:

Changelog:
- NES: added VRC1, VRC2, VRC3, MMC6 emulation
- shrink window doesn't do anything when no cartridge is loaded
- phoenix Horizontal,VerticalLayout use const Size& instead of unsigned
  width,height [for consistency]

So, all official NES ASICs are supported now. Just need sound output for
MMC5+VRC7 to complete them; and then some board re-arrangement stuff for
VRC2+MMC3.

Note that MMC6 uses the same mapper ID as MMC3, and VRC2 uses the same
ID as VRC4, so you have to make a BML board mapping or toggle which type
is chosen in the source file to use these two chips.

Side note: NES overscan clamping is obviously still assuming 16-bit, as
only half the lines are erased. Need to fix that.
2011-11-04 22:57:54 +11:00
Tim Allen
bf78e66027 Update to v083r09 release.
byuu says:

Added frequency, latency, resampler selection to the audio settings
panel (I really only wanted it there for resampler selection ... having
three options matches the driver selection style though, so whatever.)
The linear/hermite sampler will double the framerate when running Game
Boy games, and sounds the same. Same framerate and sound quality on
SNES. But it will cause buzzing in many NES titles.
Also re-added the composition { never, fullscreen, always } modes.
I think that option is clutter, but it's just impossible to get good
audio+video on Windows 7 without it ...
Lastly, HQ2x was ported over, but not very well. I just convert source
pixels from RGB888 to RGB555, and output pixels in the opposite
direction.
Need someone good to port the diff() and blend functions over to RGB888
in a way that's not terribly slow.
2011-10-31 20:55:48 +11:00
Tim Allen
483f9f8f20 Update to v083r08 release.
byuu says:

Fixed SA-1 IRQ regression for Super Mario RPG
Added turbo B,A to NES+GB; B,A,X,Y to SNES (please don't ask for turbo
L,R; you never use those keys rapidly.)
Re-added video color adjustments, which are now done in full 8-bit
colorspace for more precision

Gamma ramp option is gone. It's now the gamma option, which now only
affects the lower-half of the colors.
A value of 1.0 gives you the original, washed out colors. 1.75 is what
the gamma ramp checkbox used to do (roughly).
The new default is 1.5, which still prevents color washout, but isn't as
overly dark as before.

I wanted to make the core/interface stuff abstract the complexity of
setting up a new C++ class, but it really didn't make anything easier.
It was all one-line stubs to internal functions, and there was just too
many platform-specific things that needed to be captured, so I did away
with that. Made a base class for the ui/interface stuff to get rid of
a lot of switch(mode()) stuff, still a work in progress.
2011-10-29 18:32:20 +11:00
Tim Allen
f3feaa3e86 Update to v083r07 release.
byuu says:

Game Boy: audio should sound a lot better, eg Zelda: DX first opening
scene
Game Boy Color: now uses cothread Processor::frequency to dynamically
clock GB-CPU to 8MHz. Proper OAM DMA and timer speed. Fixes SMT: DC - WB
audio.
Added the break; statements to phoenix/windows/platform message loop
Added audio latency/frequency to config file only
2011-10-28 20:51:43 +11:00
Tim Allen
aaffd000a4 Update to v083r06 release.
byuu says:

All cores: Video classes have internal->{RGB30,24,16,15} palette
generation support
All cores: video output is now RGB24, all filters except HQ2x were
updated to reflect this (HQ2x will be very hard)
NES: MMC5 CHR mapping fixes (Bandit Kings, RTK2, Uchuu Keibitai SDF)
[Cydrak]
NES: MMC5 vertical split screen support (Uchuu Keibitai SDF) [Cydrak]
Game Boy + Game Boy Color: fixed a potential freezing bug when loading
save states (re-create cothreads on state load; was implied when using
SGB mode.)
Game Boy Color: fixed freezing bug with Zelda: LA opening (SVBK is
readable.)
Game Boy Color: more accurate colors (better than GiiBii, probably worse
than KiGB)
SNES: luminance of zero is no longer pure black, as on real hardware.
This is possible thanks to using RGB888 output now.

The current major problems I'd like to solve:
- Zelda: Link's Awakening music when Link first wakes up in the house is
  atrociously bad
- Shin Megami Tensei: Devil Children - White Book (Shiro no Sho) plays
  music at 50% speed; yet Black Book (Kuro no Sho) does not ... one of
  my favorite games, so it'd be great to fix it
2011-10-28 00:30:19 +11:00
Tim Allen
118a393c4c Update to v083r05 release.
(r04 was not posted to the WIP thread)

byuu says:

NES: passes ppu_sprite_overflow tests 01, 02, 05.
Game Boy: uses DMG BIOS (the one with the slow title scroll) or SGB
BIOS, based upon how you load the game.
Game Boy Color: Everything except the IR port is emulated. I don't have
any plans to allow linking two instances of bsnes. And that's frankly
never going to happen over netplay anyway, due to latency requirements
of the serial/IR ports.
The new DMA stuff is possibly incorrect, my test games don't seem to use
it.
Zelda: DX usually resets or crashes on the intro right before the beach
scene. I'm not sure why. Skip the intro and the game plays fine.

This is the best I can do when the most up-to-date GB/C reference
document is over ten years old and half-assed (pandocs.)
I could really use some help from anyone who understands the system.
Probably the worst part of my emulation at the moment is the interrupt
system.
Lots of things real hardware doesn't allow (DMA outside HRAM, CGB DMA to
invalid addresses, etc) isn't blocked yet.
LCD renderer is still scanline-based, which is just terrible. Doesn't
seem to be any good docs on cycle-level operation. I only know that it's
incredibly pathological and variable.
2011-10-27 11:00:17 +11:00
Tim Allen
6b708de893 Update to v083r03 release.
byuu says:

Lots of phoenix issues fixed, especially for Windows and GTK+.
NES emulation passes all ten ppu_vbl_nmi tests from blargg.
Sprite timing is nowhere near accurate yet (always consumes four clocks
per sprite), but oh well.
2011-10-24 22:35:34 +11:00
Tim Allen
db5e2107b4 Update to v083r02 release.
byuu says:

It seems impossible to pass blargg's NES ppu_vbl_nmi test 03 and 07 at
the same time. Wrote up a description of the problem here:
http://nesdev.parodius.com/bbs/viewtopic.php?p=85156#85156
2011-10-18 21:05:29 +11:00
Tim Allen
13ac6104e3 Update to v083r01 release.
byuu says:

This adds Bisqwit's NES palette generation code:

    http://nesdev.parodius.com/bbs/viewtopic.php?p=85060#85060

I set the saturation to 2.0 to closer match the existing "bright"
palette, although it still has a greater contrast range (some colors are
darker.) The gamma ramp option works now. Like SNES, best to also set
gamma to 0.8 afterward.  Once I think of a good way to expose the
saturation/hue settings, I'll do so.

I've also merged in the updated nall. Adds Cygwin uname check, and
replaces linear_vector with vector in lstring and the GUI.
2011-10-16 20:44:48 +11:00
Tim Allen
7fa8ad755d Update to v083 release.
byuu says:

This release adds preliminary Nintendo / Famicom emulation. It's only
a week or two old, so a lot of work still needs to be done before it can
compete with the most popular NES emulators.

It's important to clarify: bsnes is primarily an SNES emulator. That
will always be its forte and my core focus. I have added Game Boy
support previously for Super Game Boy emulation, and I've added NES
support mostly for something fun to work on to break up the monotony of
working on one system for seven years now. Obviously, I'd like the
emulation to be accurate and highly compatible, but I simply cannot
afford to invest the same amount of time and money into any other
systems.

Still, either way the NES and GB emulation serve as fun side-diversions,
and allow for a unified emulator interface with all of bsnes' unique
features applied to all systems. My personal favorite feature is
mightymo's extended built-in cheat code database that now also includes
NES and Game Boy codes. And it even works in Super Game Boy mode now,
too!

I'm also not worried about speed at all: so long as NES/GB are faster
than SNES/compatibility, it's fine by me. Note that due to the NES audio
running at 1.78MHz, and Game Boy audio at 4MHz stereo, a more
sophisticated audio resampler was needed: Ryphecha (Mednafen author) has
graciously written a first-rate resampler: it is a band-limited
Kaiser-windowed polyphase sinc resampler. It is combined with two
highpass filters to remove DC bias. The filter itself is SSE optimized,
but even still, approximately 50% of CPU usage for NES/GB emulation goes
to the audio filtering alone. However, you now have the best sound
possible for NES and Game Boy emulation as a result.

The GUI has also been heavily re-structured to accommodate multiple
emulators from the same interface. As such, it's quite likely a few bugs
are still lurking here and there. Please report them and I'll iron them
out for the next release.

Changelog:
- license is now GPLv3
- re-structured GUI as a multi-system emulator
- added NES emulation [byuu, Ryphecha]
- added NES ICs: MMC1, MMC2, MMC3, MMC4, MMC5, VRC4, VRC6+audio, VRC7,
  Sunsoft-5B+audio, Bandai-LZ93D50
- added NES boards: AxROM, BNROM, CNROM, ExROM, FxROM, GxROM, NROM,
  PxROM, SxROM, TxROM, UxROM
- Game Boy emulation improvements [Jonas Quinn]
- SNES core outputs full 19-bit color (4-bit luma included) for more
  accurate color reproduction (~5% speed hit)
- audio resampler is now a band-limited polyphase resampler [Ryphecha]
- cheat database includes NES+GB codes as well [mightymo, tukuyomi]
- lots of other changes
2011-10-14 21:05:25 +11:00
Tim Allen
ef85f7ccb0 Update to v082r33 release.
byuu says:

Added MMC2, MMC4, VRC4, VRC7 (no audio.)
Split NES audio code up into individual modules.
Fixed libsnes to compile: Themaister, can you please test to make sure
it works? I don't have a libsnes client on my work PC to test it.
Added about / license information to bottom of advanced settings screen
for now (better than nothing, I guess.)
Blocked PPU reads/writes while rendering for now, easier than coming up
with a bus address locking thing :/

I can't seem to fix MMC5 graphics during the intro to Uchuu Keibitai.
Without that, trying to implement vertical-split screen mode doesn't
make sense.

So as far as special audio chips go ...
* VRC6 is completed
* Sunsoft 5B has everything the only game to use it uses, but there are
  more unused channels I'd like to support anyway (they aren't
  documented, though.)
* MMC5 audio unsupported for now
* VRC7 audio unsupported, probably for a long time (hardest audio driver
  of all. More complex than core NES APU.)
* audio PCM games (Moero Pro Yakyuu!) I probably won't ever support
  (they require external WAV packs.)
2011-10-12 23:03:58 +11:00
Tim Allen
b8d607d16b Update to v082r32 release.
byuu says:

Added delay to MMC1 register writes, to fix Bill & Ted's Godawful
Adventure.
Fixed up MMC5 RAM+fill mode, and added EXRAM mode support (8x8
tiles/attributes.)
Just Breed is fully playable now.

MMC5 is a total pain in the ass, the documentation on it is just
terrible. I basically just tried seven hundred variations until
something worked.
I still need to add MMC5 vertical split screen (for one single game's
attract screen, ugh), and the extra sound channels.
Would like to rework the NES APU first. Since the pulse channels are
identical sans sweep, it'd be nice to just inherit those and mask out
the sweep register bit writes.
So that probably won't make it into the first release, at least.

Still, overall I think it'll be an impressive showing of complex mappers
for a first release: MMC3, MMC5, VRC6 and 5B. The latter two with full
audio. The only other really, really hard bit is the VRC7 audio,
supposedly.
2011-10-08 18:34:16 +11:00
Tim Allen
4c47cc203f Update to v082r31 release.
byuu says:

Enable Overscan->Mask Overscan [best I'm doing]
Video settings -> Overscan mask: (horizontal, vertical: 0-16 on each
side) [only works on NES+SNES]
BPS patching works for NES+SNES+GB; note that long-term I want BPS to
only patch headerless PRG+CHR files, but we'll need a database
/ completed board mapping system first.
MMC1 splits the board/chip markups a bit better. My attempts to emulate
the extra CHR bits per hardware fail repeatedly. Docs do not explain how
it works at all.
Emulated enough of the MMC5 to play Castlevania 3.

The MMC5 is easily the most complicated mapper the NES has to offer, and
of course, has the most pitifully vague and difficult documentation of
any mapper around.
It seems the only way anyone is able to emulate this chip is
empirically.
Everyone else apparently hooks the MMC5 right into the PPU core, which
I of course cannot do. So I had to come up with my own (probably wrong)
way to synchronize the PPU simply by observing CHR bus accesses.

I must say, I over-estimated how well fleshed out the NES hardware
documentation was. Shit hits the fan right after MMC3.
It's miles beyond the GB scene, but I find myself wanting for someone
with the technical writing ability of anomie.
I can't find anything at all on how we're supposed to support the $2007
port reads/writes without it extra-clocking the PPU's bus, which could
throw off mapper timing.
Absolutely nothing at all on the subject anywhere, something everybody
is required to do for all cycle-based emulators and ... nada.

Anyway, I'd like to refine the MMC5 a bit, getting Just Breed playable
even without sound would be really nice (it's a fun game.)
Then we need to get libsnes building again (ugh, getting worn out in
backporting changes to it.)
Once v083 is public, we can start discussing a new API for multiple
emulators.
2011-10-06 20:53:16 +11:00
Tim Allen
4cbaf4e4ec Update to v082r30 release.
byuu says:

cheats.xml -> cheats.bml, includes NES+SNES+GB codes now. Absolutely
awesome, thanks to mightymo and tukuyomi.

I also added Sunsoft-FME7/5B (with sound) emulation. Really only useful
for playing the Japanese release of Gimmick!
Fun game, but balls to the wall hard.
2011-10-05 20:37:00 +11:00
Tim Allen
21f9fe4cd5 Update to v082r29 release.
byuu says:

I doubt anyone is going to like these changes, but oh well.

The base height output for NES+SNES is now always 256x240. The Enable
Overscan option blanks out borders around the screen. This eliminates
the need for an overscan software filter. For NES, it's 16px from the
top and bottom, and 8px from the left and right. Anything less and you
get scrolling artifacts in countless games. For the SNES, it's only 16px
from the top and bottom. Main point is that most NTSC SNES games are
224-height games, so you'll have black borders. Oh well, hack the source
if you want. Game Boy overscan option does nothing.

Everything except for the cheats.xml file now uses BML markup. I need to
write a converter for cheats.xml still. Cut the SNES board parsing code
in half, 30KB->16KB. Much cleaner now.
Took the opportunity to fix a mistake I made back with the XML spec: all
numbers are integers, but can be prefixed with 0x to become hexadecimal.
Before, offset/size values defaulted to hex-mode even without a prefix,
unlike frequency/etc values.

The XML shaders have gone in their own direction anyway, with most being
multi-pass and incompatible with bsnes. So that said, please don't
extend the BML functionality from your end. But f eel free to add to the
XML spec, since other emulators now use that as well. And don't
misunderstand, I love the work that's being done there. It's pretty
awesome to see multi-pass shader capabilities, and the RAM watching
stuff is just amazing.
If there are any really awesome single-pass shaders that people would
like, I can convert it from XML and include it with future releases.
On that topic, I removed the watercolor/hdr-tv ones from the binary
packages (still in the source archive) ... they are neat, but not very
useful for actual gaming.
If we had more than one, I'd remove the Direct3D sepia one. Not going to
use shaders from a certain bipolar manic, because I'd never hear the end
of it if I did :/

Oh, one change I think people will like: MSU1 no longer requires
a memory map specification, so MSU1 authors won't have to keep updating
to my newer revisions of board markups. Basically, if there's not
a board with an msu1 section, it'll check if "gamename.msu" exists. If
it does, MSU1 gets mapped to 00-3f,80-bf:2000-2007. If all you want is
music, make a blank, zero-byte gamename.msu file.
2011-10-04 22:55:39 +11:00
Tim Allen
b629a46779 Update to v082r28 release.
byuu says:

Was mostly working on BML. Still working on the spec.
Added NES-BNROM, NES-GNROM/NES-MHROM boards. I don't even know why. The
latter games do not work very well without Zapper support.
2011-10-02 21:05:45 +11:00
Tim Allen
ba2e6b5789 Update to v082r27 release.
byuu says:

Finished porting over all mappers to board/chip disambiguations. Had to
nearly rewrite the MMC1 code to do it, but all variants should be
supported.
iNES1 is too stupid to express them all, so you'll need a board markup
if you want to play the >8KB PRG RAM games.
For whatever reason, they call VRC6's memory WRAM, and MMC1's PRG RAM.
I am calling them all PRG RAM, since it's the same damn thing.
Board spec is not going to be stable until I have a hell of a lot more
mappers implemented, so be wary of that.
Anyway, at this time, all games can be loaded sans iNES header, if you
have the board markup. I'd also like to have a board database for all
commercial titles.
I'm treating *.fc as PRG-ROM(+CHR-ROM). Will work on loading the split
files later possibly.
2011-10-01 22:06:48 +10:00
Tim Allen
7115047d85 Update to v082r26 release.
byuu says:

.cht files now use BML-formatted data. I'm still going to request the
cheats.xml file as-is, and will write my own converter for embedding
during releases.
This is where parsing 2MB markup files in 10ms is really going to be
nice. Had to use an evil hack before for actually searching for games.

This has the start of the board/chip separation from mappers for NES,
and it has a barebones iNES->board markup converter.
You can specify your own board markup and bypass the need for an iNES
header, so in other words it will load No-Intro style games with
a proper board file.
Long-term, we'll have an internal database for commercial boards, and
probably folder.fc/prg.rom{,chr.rom} loading support.

Since they can't co-exist, the mappers are currently disabled, and I've
only ported the easy ones. So no MMC1/MMC3/VRC6 in this release. I need
to make them into chips first.
2011-10-01 21:16:57 +10:00
Tim Allen
e8b1af0917 Update to v082r25 release.
byuu says:

Ryphecha fixed Gun Nac, it was some sort of problem with blank sprite
address fetching messing with the MMC3
I've started on an XML parser for iNES-free loading, but it's pretty
barebones right now. Only NROM-256 loads, and you have to make it
a compile-time thing (so other games work for now.)
Updated nall with nullptr stuff.
nall/detect is now nall/intrinsic and has both #defines + static
constants that can be used to detect the platform (allows for run-time
platform checks where practical.)
ruby has a Makefile now, that makes using it in other projects a lot
easier
2011-09-29 22:08:22 +10:00
Tim Allen
1d4f778176 Update to v082r24 release.
byuu says:

Upgraded to GCC 4.6.1.
Removed nall/foreach and nall/concept, upgraded iterator support on all
of my containers, and replaced everything with range-for.
Fixed up Qt geometry a good bit, should at least create windows now without bouncing around.
Added some initial nullptr / constexpr changes.
Some other minor cleanups ... removing foreach() took about 6-8 hours
alone.
2011-09-27 21:55:02 +10:00
Tim Allen
875ed46d79 Update to v082r23 release.
byuu says:

Ryphecha fixed the FF1 glitch, added two highpass filters to NES audio
output (still working on a lowpass), and fixed VRC6 audio issues.
I reduced the complexity of all eight supported mapping modes, and
standardized them; and added in an overscan filter (not in archive) for
chopping off all the NES edge garbage (8 pixels on the left and right,
16 on the top and bottom.)
It's extreme, but anything less shows junk. I may make this part of the
menu option, just clip off more on NES mode than SNES mode.
2011-09-26 21:38:57 +10:00
Tim Allen
046e478d86 Update to v082r22 release.
byuu says:

Mappers are now optionally threaded.
Fixed up MMC3 emulation, SMB3 and MM3-6 are all fully playable. However,
many unusual variants of this chip are not supported still.
Added UNROM+UOROM for Contra and MM1, allowing all six MM games to play
now.
Added VRC6 with sound emulation, because I wanted to get audio mixing in
place.

Chose VRC6 because it has Esper Dream 2, which is an absolutely amazing
game that everyone should play :D
The game didn't use sawtooth, and I didn't test any other VRC6 games, so
hopefully that is emulated passably well enough.
2011-09-26 21:27:06 +10:00
Tim Allen
82a17ac0f5 Update to v082r21 release.
byuu says:

2-6% speed hit in SNES core for outputting 19-bit (rounded to 32-bit ...
sigh) video, so that luma non-linearity can eventually be emulated
properly.
Now using sinc audio resampler, massive speed hit of course to NES+GB
only, but it's required to get rid of aliasing (buzzing) present in
many, many games otherwise.
Fixed fast forward and none/blur select.
Finally fixed texture clearing for changing pixel shaders and video
filters.
Some realllly basic NES MMC3, extremely broken so don't bug me about it.
Other stuff, probably.
2011-09-24 19:51:08 +10:00
Tim Allen
979aa640af Update to v082r20 release.
byuu says:

NES now has save state support.
NES A/B buttons were indeed swapped, so that's fixed now.
nall/dsp now puts resamplers into separate classes, so that each can
have their own state information.
opengl.hpp uses GL_RGBA internal format and doesn't regenerate textures
on resize. No speedup, no fix to junk on resize, so I will be very
unhappy if this breaks things for anyone.
GLSL shaders use <fragment filter="nearest/point"> as you guys wanted.
ui-snes was removed.
2011-09-23 21:13:57 +10:00
Tim Allen
98ec338285 Update snesfilter to release 20110920.
This was released beside bsnes v082r19. byuu didn't mention it in the
v082r19 release notes, but in a previous post mentioned that a number of
filters stopped working when bsnes switched to using RGB555 for all its
internal data.
2011-09-22 10:03:11 +10:00
Tim Allen
5b4dcbfdfe Update to v082r19 release.
byuu says:

This will be the last release with the ui-snes folder (it's broken now
anyway.)
Re-added cheat code database searching + add window. It hashes
NES+SNES+GB images now and will look them up in the database.
Re-added filter support, all filters now output at RGB555. Stacking is
possible, but I don't currently allow it.
Removed mouse capture + test options from tools menu.
Removed smooth video output from settings menu.
There are now two built-in "shaders": "None" (point filtering) and
"Blur" (linear filtering).
OpenGL shaders can now use <shader language="GLSL" filter="point"> (or
"linear") to specify their filtering mode.
Individual emulator versions are gone, and names are hidden from the
GUI: you just see bsnes v082.19 now. A new release bumps all core
versions.

I cannot for the life of me get the video to clear properly when
toggling the shaders. Say you set pixellate2x filter, then turn on
curvature shader, then turn off the filter, you get junk at the bottom
right.
I have tried clearing and flipping the OpenGL surface 64x in a row ...
I don't know where the hell it's getting the data from. If anyone can
make a small patch to fix that, I'd greatly appreciate it.
2011-09-22 10:00:21 +10:00
Tim Allen
101c9507b1 Update to v082r18 release.
byuu says:

There we go, the GUI is nearly feature-complete once again.

All cores now output their native video format (NES={emphasis}{palette},
SNES=BGR555, GameBoy={ bright, normal, darker, darkest }), and are
transformed to RGB555 data that is passed to the video renderer.
The video renderer then uses its internal palette to apply
brightness/contrast/gamma/ramp adjustments and outputs in RGB888 color
space.
This does add in another rendering pass, unfortunately, but it's
a necessary one for universal support.
The plan is to adapt all filters to take RGB555 input, and output RGB555
data as well. By doing this, it will be possible to stack filters.
However, it's a bit complicated: I need to plan how the stacking should
occur (eg we never want to apply scanlines before HQ2x, etc.)
Added input frequency adjustments for all three systems. I can easily
get perfect video/audio sync on all three now, hooray.
Long-term, it seems like we only really need one, and we can do
a video/audio delta to get an adjusted value. But for now, this gets the
job done.
Added audio volume adjust. I left out the balance for now, since it's
obviously impossible to balance the NES' single channel audio (I can
duplicate the channel, and do twice the filtering work, but ... why?)
I replaced NTSC/PAL TV mode selection with an "Enable Overscan"
checkbox. On, you get 240 lines on NES+SNES. Off, you get 224 lines on
NES+SNES.
Also added aspect correction box back. I don't do that gross PAL
distortion shit anymore, sorry PAL people. I just scale up the
54/47*(240/224) aspect correction for overscan off mode.
All memory is loaded and saved now, for all three systems (hooray, now
you can actually play Zelda 1&2.)
Added all of the old bsnes hotkeys, with the exception of capture
screenshot. May add again later. May come up with something a bit
different for extra features.
Re-added the NSS DIP switch setting window. Since geometry is saved,
I didn't want to auto-hide rows, so now you'll see all eight possible
DIPs, and the ones not used are grayed out.
Ultimately, nobody will notice since we only have DIPs for ActRaiser
NSS, and nobody's probably even using the XML file for that anyway.
Whatever, it's nice to have anyway.
Took FitzRoy's advice and single-item combo boxes on the input selection
are disabled, so the user doesn't waste time checking them.
I wanted to leave text so that you know there's not a problem. Qt
disabled radio box items look almost exactly like enabled ones.
Fixed lots of issues in phoenix and extended it a bit. But I was still
having trouble with radio box grouping, so I said fuck it and made the
panels show/hide based instead of append/remove based.
That's all for stuff off the checklist, I did a bunch of other things
I don't recall.

So yeah, I'd say the GUI is 100% usable now. This is my opinion on how
multi-platform GUIs should be done =)

Oh, I figure I should mention, but the NES core is GPLv3, and all future
SNES+GB releases will be as well. It's a move against Microsoft's Metro
store.
2011-09-21 00:04:43 +10:00
Tim Allen
69ed35db99 Update to v082r17 release.
byuu says:

Adds BS-X/Slotted/SufamiTurbo/SGB cartridge loading. Calling it
Satellaview as I'm more partial to that at the moment.
FileBrowser now remembers your folders per filter type like before, and
will keep your place in the list if you don't switch away.
I wanted there to be ONE slot loader, so the loader will show a grayed
out secondary slot on non-ST loading, but it's more consistent to only
have one window instead of two for geometry placement.
Removed help menu. Will try and work it in somewhere unobtrusive later
on I suppose.
Added timed messages and the usual "no cart loaded / paused" messages
and such.
2011-09-19 22:34:18 +10:00
Tim Allen
5c2d16828c Update to v082r16 release.
byuu says:

Binary output is once again called bsnes. No versioning on the title
without a system cartridge loaded. Still saving config files to
.config/batch for now.
Finally fixed NES APU frame IRQ clearing on $4015 reads.
Added mouse button/axis binding through buttons on the input capture
window.
Added advanced settings window with driver selection and focus policy
settings. Will show your default driver properly if none are selected
now, unlike old bsnes.
That exposed a small bug where phoenix isn't removing widgets on
Layout::remove, worked around it for now by hiding the panels. Damn,
sick of working on phoenix.
Added all missing input controllers, which can all now be mapped, and
bound them to the main menu, and added NES support for selecting "no
connected controller."
Added mouse capture and the requisite tools menu option for it.
Added WindowManager class that keeps track of both position and size now
(eg full geometry), so now you can resize your windows and save the
settings, unlike old bsnes.
WindowManager has more stringent geometry checks. The *client area* (not
the window border) can't be below 0,0 or above the width/height of three
30" monitors. If you have 4+ 30" monitors, then fuck you :P
settings.cfg is now also saved, captures all currently available
settings. Right now, there's only one path for the file browser to
remember. I will probably make this per-system later.
FileBrowser has been made a bit more friendly. The bottom left tells you
what type of files the list is filtered by (so you see "*.sfc" for
SNES), and the bottom right has an open button that can enter folders or
load files.
Added video shader support.
Fixed nall/dsp variadic-channel support, was only outputting the left
channel.
2011-09-19 22:25:56 +10:00
Tim Allen
382ae1e61e Update to v082r15 release.
byuu says:

7.5 hours of power coding. Das Keyboard definitely helped (but didn't
eliminate) RSI, neato.

Okay, the NES resampler was using 315 / 88.8 by mistake, so the output
rate was wrong, causing way more video/audio stuttering than necessary.
STILL forgot the NES APU frame IRQ clear thing on $4015 reads, blah. Why
do I always remember things right after uploading the WIPs?
Recreated the input manager with a new design, works much nicer than the
old one, a whole lot less duplicated code.
Recreated the input settings window to work with the new multi-system
emulation.
All input settings are saved to their own configuration file, input.cfg.
Going to batch folder for now.

Okay, so the new input settings window ... basically there are now three
drop-downs, and I'm not even trying to label them anymore.
They are primary, secondary, tertiary selectors for the listed group
below. Examples:
"NES -> Controller Port 1 -> Gamepad"
"SNES -> Controller Port 2 -> Super Scope"
"User Interface -> Hotkeys -> Save States"

I am aware that "Clear" gets disabled when assigning. I will work on
that later, being lazy for now and disabling the entire window. Have to
add the mouse binders back, too.
Escape and modifiers are both mappable as individual keys now. If you
want to clear, click the damn clear button :P

Oh, and all input goes to all windows for now. That'll be fixed too when
input focus stuff is re-added.
2011-09-17 16:42:17 +10:00
Tim Allen
7619805266 Update to v082r14 release.
byuu says:

Emulates DMC channel (sound effect when Link gets hit in Zelda 1, for
instance), fixes up bugs in rectangle/sweep and triangle channels, adds
DMC/frame APU IRQs, adds proper stalling for DMC ROM reads (should even
be cycle accurate, but has one extra cycle when triggered during OAM
DMA, I think), but forgets the frame IRQ acknowledge clear on $4015 read
(will fix next WIP.) All sound courtesy of Ryphecha.

Made template configuration settings window (empty for now.) Simplified
SNES cheat.cpp code. Some other stuff.

Further developed RSI.
2011-09-16 21:44:07 +10:00
Tim Allen
e3c7bbfb63 Update to v082r13 release.
byuu says:

I've updated the {System}::Interface classes to encapsulate all common
functionality, so they are C++ equivalents of libsnes now.
The idea being, use the interface class and you'll never have to reach
into core objects (unless you really want to.)
Not guaranteeing as stable an API as I do with libsnes for that, though.
C++ doesn't make for nice dynamic libraries, anyway.

Added back the state manager, and it now works for both SNES and Game
Boy. NES save states aren't in yet.
Anyway, this should give you a pretty good feel for what the combined UI
is going to be like: same as before, everything works the same. Only
difference is the dynamic system menu and cartridge menu with more load
options. The settings window will be mostly the same as well, but will
obviously have options that only apply to some systems.
2011-09-16 21:30:45 +10:00
Tim Allen
5f099b8ad0 Update to v082r12 release.
byuu says:

Merged Ryphecha's APU emulation, so NES has sound output now. We are
still missing the DMC memory fetch, so there will be missing sound
effects here and there.
2011-09-15 22:33:26 +10:00
Tim Allen
cb3460a673 Update to v082r11 release.
byuu says:

Emulates grayscale and color emphasis modes, improves sprite timing and
PPU bus fetching behavior with the PPU disabled.
2011-09-15 22:27:34 +10:00
Tim Allen
278cf8462c Update to v082r10 release.
byuu says:

Emulated the Game Genie for the NES and Game Boy, and wrote a new cheat
editor that doesn't reach into specific emulation cores so that it would
work.
Before you ask: yes, long-term I'd like Super Game Boy mode to accept
Game Boy codes. But that's not high on the priority list.
Renamed the mappers toward board names, LZ...->BandaiFCG,
LS161...->AOROM, added CNROM emulation (Adventure Island, Gradius, etc.)
Added the tools menu load/save state stuff, but note that the NES
doesn't have save state support yet (waiting for the interface to
stabilize a bit more first.)

Note: this will be the last release to have the ui-gameboy folder, it's
been deleted locally from my end, as the new multi-GUI does all that it
does and more now.
2011-09-15 22:23:13 +10:00
Tim Allen
7f4381b505 Update to v082r09 release.
byuu says:

Skip this build if you can, it's CPU+PPU timing improvements that should
not visibly affect anything.
2011-09-15 22:14:07 +10:00
Tim Allen
c668d10ac7 Update to v082r08 release.
byuu says:

Fixed up the PPU to be as close to cycle-perfect as possible. Fixed RMW
to write twice instead of read twice. Ryphecha added AOROM and fixed up
MMC1. Have CNROM too, but I need to rethink the mapper/board
distinction. Apparently the same logic IC is used in both AOROM and
CNROM, and it's just a matter of routing the pins to it. I need to
consider how crazy it'd be to emulate the logic IC and have boards
simply reroute pins to it. If it's too much work, we'll just treat
mappers as board + logic IC combinations. We'll see.
2011-09-12 20:30:44 +10:00
Tim Allen
7fc78dae07 Update to v082r07 release.
byuu says:

Wrote a cycle-based PPU renderer, ~95% hardware accurate for BGs, not so
much for sprites yet. Mednafen has been helping out a lot.
2011-09-12 20:17:12 +10:00
Tim Allen
4ca051a22f Update to v082r06 release.
byuu says:

Emulated MMC1, currently defaults to B2 configuration. Fixed a whole
bunch of timing things, render things, nametable mirroring things, etc.
Zelda and Mega Man II are fully playable, but they have odd vertical
scrolling issues that make it a not so fun experience.
Not sure what the problem is there, yet. The Y scroll register writes
seem to be wonky ... I don't know.

Keeping the Cartridge menu always visible now, so it's faster to load
carts, but I am still hiding the non-loaded system menus.
2011-09-12 19:44:22 +10:00
Tim Allen
8618334356 Update to v082r05 release.
byuu says:

Okay, I fixed up many outstanding phoenix issues.
* Windows/GTK+ fixed by using processEvents instead of main(); Windows can run unthrottled, and GTK+ shows the window contents now
* fixed keyboard beeping once and for all on Windows: I now whitelist tabbable controls
* fixed main menubar setVisible calls
* Qt and GTK+ now allow you to resize windows smaller than they initially were

Both Qt and GTK+ still fuck up the geometry a bit when toggling fullscreen mode. I have tried, and tried, and tried and tried and tried to fix it all. Nothing works. I give up.
Easier to destroy and recreate the fucking window than figure out how to resize it on Linux (and no, I can't do that. ruby would not like the handle changing.)

As for the GUI:
* file browser is back in, still need remember place and open folder code; that needs to be extended to handle multiple systems now
* shrink window command added to tools menu.
2011-09-09 14:16:25 +10:00
Tim Allen
ec7e4087fb Update to v082r04 release.
byuu says:

So, here's the deal. I now have three emulators. I don't think the
NES/GB ones are at all useful, but I do want them to be eventually. And
having them have those pathetic little GUIs like ui-gameboy, and keeping
everything in separate project folders, just doesn't work well for me.
I kind of "got around" the issue with the Game Boy, by only allowing SGB
mode emulation. But there is no "Super Nintendo" ... er ... wait ...
uhmm ... well, you know what I mean anyway.

So, my idea is to write a multi-emulator GUI, and keep the projects
together. The GUI is not going to change much. The way I envision this
working:

At startup, you have a menubar with: "Cartridge, Settings, Tools, Help".
Cartridge has "Load NES Cartridge", "Load SNES Cartridge", etc.
When you load something, Cartridge is replaced with the appropriate
system menu, eg "SNES". Here you have all your regular items: "power,
reset, controller port selection, etc." There is also a new "Unload
Cartridge" option, which is how you restore the "Cartridge" menu again.
I have no plans to emulate any other systems, but if I ever do emulate
something that doesn't take cartridges, I'll change the name to just
"Load" or something.

The cheat editor / state manager will look and act exactly the same. The
settings panel will look exactly the same. I'll simply show/hide
system-specific options as needed, like NES/SNES aspect ratio
correction, etc. The input mapping window will just have settings for
the currently loaded system. Video and audio tweaking will apply
cross-system, as will hotkey mapping.

The GUI stuff is mostly copy-paste, so it should only take me a week to
get it 95% back to where it was, so don't worry, this isn't total GUI
rewrite #80.
I am, however, making all the objects pointers, so that I can destruct
them all prior to main() returning, which is certainly one way of fixing
that annoying Windows/Qt crash.

Please only test on Linux. The Windows port is broken to hell, and will
give you a bad impression of the idea:
- menu groups are not hiding for some reason (all groups are showing, it
  looks hideous)
- Timer interval(0) is taking 16ms per call, capping the FPS to ~64 tops
  [FWIW, bsnes/accuracy gets 130fps, bgameboy gets 450fps, bnes gets
  800fps; all run at lowest possible granularity]
- the OS keeps beeping when you press keys (AGAIN)

Of course, Qt and GTK+ don't let you shrink a window from the requested
geometry size, because they suck. So the video scaling stuff doesn't
work all that great yet.
Man, a metric fuckton of things need to be fixed in phoenix, and
I really don't know how to fix any of them :/
2011-09-09 14:08:38 +10:00
Tim Allen
496708cffe Update to v082r03 release.
byuu says:

Couple more fixes to audio from Jonas, and I converted all types from
"unsigned" to the smallest sizes possible, which simplified a bit of the
code. Love variable-length integers.

Audio core should be really good now. Not perfect, but pretty close for
99% of games. Also fixed the window stuff for Cool World and such.
2011-09-05 13:56:22 +10:00
Tim Allen
a86c5ee59d Update to v082r02 release.
byuu says:

Has Jonas Quinn's many Game Boy APU fixes, and two more from blargg's
notes that I added.

It also has the new dynamic phoenix. So yeah, it'll crash on bsnes/Qt
exit. If anyone can fix it *properly* and wants the money, I'll pay them
$20 for the trouble =)
2011-09-05 13:48:23 +10:00
Tim Allen
d8f9204e18 Update to v082r01 release.
byuu says:

Changelog:
- if config file window coordinates are >= 30000, it snaps them back to
  128,128; should end the "why aren't windows visible?" posts
- updated GUI code to match new phoenix changes
- phoenix: Layout and Widget inherit from Sizable; directional layouts
  make no distinction between widgets and layouts
- phoenix: individual widgets / layout can maintain visible/hidden
  status in spite of their parents' visibility
2011-08-22 21:27:04 +10:00
Tim Allen
e8775319c8 Update to v082 release.
byuu says:

This release features many substantial Game Boy emulation improvements
(all courtesy of Jonas Quinn), a new audio DSP class, and BPS patching
support.

Changelog (since v081):

- added new DSP audio engine; supports sample-averaging for the Game
  Boy's high frequency rate
- GB: MMM01 images with boot loader at bottom of ROM can now be loaded
- GB: EI is delayed one cycle; fixes Bubble Bobble [Jonas Quinn]
- GB: fixed window -7 offset behavior; fixes Contra 3 first boss [Jonas
  Quinn]
- GB: disable LCD interrupts when rendering is off; fixes Super Mario
  Land 2 [Jonas Quinn]
- GB: fixed noise channel LFSR; fixes Zelda: LA lightning sound [Jonas
  Quinn]
- GB: square channels use initial_length like the noise channel [Jonas
  Quinn]
- UI: added BPS patching support; removed UPS patching support
- UI: when loading BS-X/Sufami Turbo/Game Boy games; display game title
  instead of BIOS title
- UI: modified timestamps on screenshots for Windows/NTFS (which
  disallows use of ':')
2011-08-21 01:02:27 +10:00
Tim Allen
095181af62 Update to v081r04 release.
byuu says:

- GB: square channels cache initial_length and invert the length value
  on writes [Jonas Quinn]
- GB: updated LCD disable to just ignore interrupts and pixel writes,
  fixes Contra
- BPS patch information and cartridge sections are now copied from the
  patch metadata, if it is present
- fixed bpslinear out-of-bounds issue, which will be in snespurify v11
  official [Danish]
- simplified Makefile again since command-line trumps manual assignments
2011-08-19 21:36:26 +10:00
Tim Allen
b28c54770c Update to v081r03 release.
byuu says:

- GameBoy: fixed window behavior for Contra 3 first boss [Jonas Quinn]
- GameBoy: fixed noise channel for Zelda: LA intro [Jonas Quinn]
- GameBoy: completely disable LCD when turned off, no interrupts; fixes
  Super Mario Land 2 [thanks to Jonas Quinn]
- GameBoy: keep track of where pixels come from for OBJ priority mode
  [Jonas Quinn]
- updated mode+slot-dependent name handling: simplifies Path class,
  allows SGB/BSX/ST games to show the slot title (instead of BIOS name)
  on the title bar
- Makefile allows command-line definitions for ui and profile now (make
  profile=compatibility ui=ui-libsnes)
- Makefile now allows (make pgo=instrument) and (make pgo=optimize)
- added BPS patching support, removed UPS patching support
2011-08-18 23:58:27 +10:00
Tim Allen
71763f2d98 Update to v081r02 release.
byuu says:

This release adds nall/dsp, which is a new framework for audio DSP
effects. It currently supports the usual fractional hermite resampling
and volume adjustments from ruby; but it also adds balance, and
arbitrary precision input and output (still limited to two channels, and
still signed audio [just subtract 1<<(bits-1) from an unsigned value.])
Internally, all samples are converted to doubles in the range of -1.0 to
+1.0, to allow for far greater precision with the hermite resampler and
volume/balance/etc adjustments.

As a result of this, all of the extra resampling/volume code from
ruby::audio has been removed. bsnes pulls in a copy of nall::dsp to
handle that stuff now.
2011-08-14 20:34:11 +10:00
Tim Allen
423d9ba00d Update to v081r01 release.
byuu says:

- EI takes an extra cycle to raise IME; fixes Bubble Bobble [for the
  Gameboy], and likely many more games [Jonas Quinn]
- nall/gameboy/cartridge.hpp descrambles MMM01 images (header can be at
  top or bottom of image now)
- screenshot timestamps use filename-yyyy-mm-dd hh.mm.ss.bmp format; so
  that they can save on Windows
- closing the emulator will switch to windowed mode first, so that
  geometry is preserved
2011-08-13 13:51:29 +10:00
1002 changed files with 162211 additions and 87016 deletions

View File

@@ -1,41 +1,45 @@
include nall/Makefile
nes := nes
snes := snes
gameboy := gameboy
profile := accuracy
ui := ui
# phoenix := gtk
target := ui
# options += debugger
# options += console
# compiler
c := $(compiler) -std=gnu99
cpp := $(subst cc,++,$(compiler)) -std=gnu++0x
flags := -O3 -fomit-frame-pointer -I.
flags := -I. -O3 -fomit-frame-pointer
link :=
objects := libco
# profile-guided instrumentation
# flags += -fprofile-generate
# link += -lgcov
# profile-guided optimization mode
# pgo := instrument
# pgo := optimize
# profile-guided optimization
# flags += -fprofile-use
flags := $(flags) $(foreach o,$(call strupper,$(options)),-D$o)
ifeq ($(pgo),instrument)
flags += -fprofile-generate
link += -lgcov
else ifeq ($(pgo),optimize)
flags += -fprofile-use
endif
# platform
ifeq ($(platform),x)
link += -s -ldl -lX11 -lXext
else ifeq ($(platform),osx)
else ifeq ($(platform),win)
link += -mwindows
# link += -mconsole
link += $(if $(findstring console,$(options)),-mconsole,-mwindows)
link += -mthreads -s -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 -lole32
link += -enable-stdcall-fixup -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc
link += -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc
else
unknown_platform: help;
endif
ui := target-$(target)
# implicit rules
compile = \
$(strip \
@@ -54,6 +58,7 @@ all: build;
obj/libco.o: libco/libco.c libco/*
include $(ui)/Makefile
flags := $(flags) $(foreach o,$(call strupper,$(options)),-D$o)
# targets
clean:
@@ -69,7 +74,23 @@ clean:
-@$(call delete,*.pdb)
-@$(call delete,*.manifest)
sync:
if [ -d ./libco ]; then rm -r ./libco; fi
if [ -d ./nall ]; then rm -r ./nall; fi
if [ -d ./ruby ]; then rm -r ./ruby; fi
if [ -d ./phoenix ]; then rm -r ./phoenix; fi
cp -r ../libco ./libco
cp -r ../nall ./nall
cp -r ../ruby ./ruby
cp -r ../phoenix ./phoenix
rm -r libco/doc
rm -r libco/test
rm -r nall/test
rm -r ruby/_test
rm -r phoenix/nall
rm -r phoenix/test
archive-all:
tar -cjf bsnes.tar.bz2 data gameboy libco nall obj out phoenix ruby snes ui ui-gameboy ui-libsnes Makefile cc.bat clean.bat sync.sh
tar -cjf bsnes.tar.bz2 base data gameboy libco nall nes obj out phoenix ruby snes target-debugger target-libsnes target-ui Makefile cc.bat purge.bat
help:;

129
bsnes/base/base.hpp Executable file
View File

@@ -0,0 +1,129 @@
#ifndef BASE_HPP
#define BASE_HPP
const char Version[] = "087";
#include <nall/platform.hpp>
#include <nall/algorithm.hpp>
#include <nall/any.hpp>
#include <nall/array.hpp>
#include <nall/dl.hpp>
#include <nall/dsp.hpp>
#include <nall/endian.hpp>
#include <nall/file.hpp>
#include <nall/function.hpp>
#include <nall/moduloarray.hpp>
#include <nall/priorityqueue.hpp>
#include <nall/property.hpp>
#include <nall/random.hpp>
#include <nall/serializer.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
#include <nall/utility.hpp>
#include <nall/varint.hpp>
#include <nall/vector.hpp>
using namespace nall;
//debugging function hook:
//no overhead (and no debugger invocation) if not compiled with -DDEBUGGER
//wraps testing of function to allow invocation without a defined callback
template<typename T> struct hook;
template<typename R, typename... P> struct hook<R (P...)> {
function<R (P...)> callback;
R operator()(P... p) const {
#if defined(DEBUGGER)
if(callback) return callback(std::forward<P>(p)...);
#endif
return R();
}
hook() {}
hook(const hook &hook) { callback = hook.callback; }
hook(void *function) { callback = function; }
hook(R (*function)(P...)) { callback = function; }
template<typename C> hook(R (C::*function)(P...), C *object) { callback = { function, object }; }
template<typename C> hook(R (C::*function)(P...) const, C *object) { callback = { function, object }; }
template<typename L> hook(const L& function) { callback = function; }
hook& operator=(const hook& hook) { callback = hook.callback; return *this; }
};
#if defined(DEBUGGER)
#define privileged public
#else
#define privileged private
#endif
typedef int1_t int1;
typedef int2_t int2;
typedef int3_t int3;
typedef int4_t int4;
typedef int5_t int5;
typedef int6_t int6;
typedef int7_t int7;
typedef int8_t int8;
typedef int9_t int9;
typedef int10_t int10;
typedef int11_t int11;
typedef int12_t int12;
typedef int13_t int13;
typedef int14_t int14;
typedef int15_t int15;
typedef int16_t int16;
typedef int17_t int17;
typedef int18_t int18;
typedef int19_t int19;
typedef int20_t int20;
typedef int21_t int21;
typedef int22_t int22;
typedef int23_t int23;
typedef int24_t int24;
typedef int25_t int25;
typedef int26_t int26;
typedef int27_t int27;
typedef int28_t int28;
typedef int29_t int29;
typedef int30_t int30;
typedef int31_t int31;
typedef int32_t int32;
typedef int64_t int64;
typedef uint1_t uint1;
typedef uint2_t uint2;
typedef uint3_t uint3;
typedef uint4_t uint4;
typedef uint5_t uint5;
typedef uint6_t uint6;
typedef uint7_t uint7;
typedef uint8_t uint8;
typedef uint9_t uint9;
typedef uint10_t uint10;
typedef uint11_t uint11;
typedef uint12_t uint12;
typedef uint13_t uint13;
typedef uint14_t uint14;
typedef uint15_t uint15;
typedef uint16_t uint16;
typedef uint17_t uint17;
typedef uint18_t uint18;
typedef uint19_t uint19;
typedef uint20_t uint20;
typedef uint21_t uint21;
typedef uint22_t uint22;
typedef uint23_t uint23;
typedef uint24_t uint24;
typedef uint25_t uint25;
typedef uint26_t uint26;
typedef uint27_t uint27;
typedef uint28_t uint28;
typedef uint29_t uint29;
typedef uint30_t uint30;
typedef uint31_t uint31;
typedef uint32_t uint32;
typedef uint_t<33> uint33;
typedef uint64_t uint64;
typedef varuint_t varuint;
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3812
bsnes/data/laevateinn.hpp Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,12 @@
gameboy_objects := gameboy-system gameboy-scheduler
options += gameboy
gameboy_objects := gameboy-interface gameboy-system gameboy-scheduler
gameboy_objects += gameboy-memory gameboy-cartridge
gameboy_objects += gameboy-cpu gameboy-apu gameboy-lcd
gameboy_objects += gameboy-cheat gameboy-video
objects += $(gameboy_objects)
obj/gameboy-interface.o: $(gameboy)/interface/interface.cpp $(call rwildcard,$(gameboy)/interface/)
obj/gameboy-system.o: $(gameboy)/system/system.cpp $(call rwildcard,$(gameboy)/system/)
obj/gameboy-scheduler.o: $(gameboy)/scheduler/scheduler.cpp $(call rwildcard,$(gameboy)/scheduler/)
obj/gameboy-cartridge.o: $(gameboy)/cartridge/cartridge.cpp $(call rwildcard,$(gameboy)/cartridge/)
@@ -10,3 +14,5 @@ obj/gameboy-memory.o: $(gameboy)/memory/memory.cpp $(call rwildcard,$(gameboy)/m
obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/)
obj/gameboy-apu.o: $(gameboy)/apu/apu.cpp $(call rwildcard,$(gameboy)/apu/)
obj/gameboy-lcd.o: $(gameboy)/lcd/lcd.cpp $(call rwildcard,$(gameboy)/lcd/)
obj/gameboy-cheat.o: $(gameboy)/cheat/cheat.cpp $(call rwildcard,$(gameboy)/cheat/)
obj/gameboy-video.o: $(gameboy)/video/video.cpp $(call rwildcard,$(gameboy)/video/)

View File

@@ -46,16 +46,18 @@ void APU::main() {
noise.run();
master.run();
system.interface->audio_sample(master.center, master.left, master.right);
if(++clock >= 0) co_switch(scheduler.active_thread = cpu.thread);
interface->audioSample(master.center, master.left, master.right);
clock += 1 * cpu.frequency;
if(clock >= 0) co_switch(scheduler.active_thread = cpu.thread);
}
}
void APU::power() {
create(Main, 4194304);
create(Main, 4 * 1024 * 1024);
for(unsigned n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
foreach(n, mmio_data) n = 0x00;
for(auto &n : mmio_data) n = 0x00;
sequencer_base = 0;
sequencer_step = 0;
@@ -80,10 +82,10 @@ uint8 APU::mmio_read(uint16 addr) {
if(addr == 0xff26) {
uint8 data = master.enable << 7;
if(square1.counter && square1.length) data |= 0x01;
if(square2.counter && square2.length) data |= 0x02;
if( wave.counter && wave.length) data |= 0x04;
if( noise.counter && noise.length) data |= 0x08;
if(square1.enable) data |= 0x01;
if(square2.enable) data |= 0x02;
if( wave.enable) data |= 0x04;
if( noise.enable) data |= 0x08;
return data | table[addr - 0xff10];
}

View File

@@ -8,28 +8,19 @@ void APU::Master::run() {
return;
}
signed sample = 0, channels;
signed sample = 0;
sample += apu.square1.output;
sample += apu.square2.output;
sample += apu.wave.output;
sample += apu.noise.output;
sample >>= 2;
center = sclamp<16>(sample);
if(left_enable == false && right_enable == false) {
left = center;
right = center;
return;
}
center = (sample * 512) - 16384;
sample = 0;
channels = 0;
if(channel1_left_enable) { sample += apu.square1.output; channels++; }
if(channel2_left_enable) { sample += apu.square2.output; channels++; }
if(channel3_left_enable) { sample += apu.wave.output; channels++; }
if(channel4_left_enable) { sample += apu.noise.output; channels++; }
if(channels) sample /= channels;
left = sclamp<16>(sample);
if(channel1_left_enable) sample += apu.square1.output;
if(channel2_left_enable) sample += apu.square2.output;
if(channel3_left_enable) sample += apu.wave.output;
if(channel4_left_enable) sample += apu.noise.output;
left = (sample * 512) - 16384;
switch(left_volume) {
case 0: left >>= 3; break; // 12.5%
@@ -41,16 +32,13 @@ void APU::Master::run() {
case 6: left -= (left >> 3); break; // 87.5%
//case 7: break; //100.0%
}
if(left_enable == false) left = 0;
sample = 0;
channels = 0;
if(channel1_right_enable) { sample += apu.square1.output; channels++; }
if(channel2_right_enable) { sample += apu.square2.output; channels++; }
if(channel3_right_enable) { sample += apu.wave.output; channels++; }
if(channel4_right_enable) { sample += apu.noise.output; channels++; }
if(channels) sample /= channels;
right = sclamp<16>(sample);
if(channel1_right_enable) sample += apu.square1.output;
if(channel2_right_enable) sample += apu.square2.output;
if(channel3_right_enable) sample += apu.wave.output;
if(channel4_right_enable) sample += apu.noise.output;
right = (sample * 512) - 16384;
switch(right_volume) {
case 0: right >>= 3; break; // 12.5%
@@ -62,18 +50,17 @@ void APU::Master::run() {
case 6: right -= (right >> 3); break; // 87.5%
//case 7: break; //100.0%
}
if(right_enable == false) right = 0;
}
void APU::Master::write(unsigned r, uint8 data) {
if(r == 0) {
left_enable = data & 0x80;
left_volume = (data >> 4) & 7;
right_enable = data & 0x08;
right_volume = (data >> 0) & 7;
if(r == 0) { //$ff24 NR50
left_in_enable = data & 0x80;
left_volume = (data >> 4) & 7;
right_in_enable = data & 0x08;
right_volume = (data >> 0) & 7;
}
if(r == 1) {
if(r == 1) { //$ff25 NR51
channel4_left_enable = data & 0x80;
channel3_left_enable = data & 0x40;
channel2_left_enable = data & 0x20;
@@ -84,15 +71,15 @@ void APU::Master::write(unsigned r, uint8 data) {
channel1_right_enable = data & 0x01;
}
if(r == 2) {
if(r == 2) { //$ff26 NR52
enable = data & 0x80;
}
}
void APU::Master::power() {
left_enable = 0;
left_in_enable = 0;
left_volume = 0;
right_enable = 0;
right_in_enable = 0;
right_volume = 0;
channel4_left_enable = 0;
channel3_left_enable = 0;
@@ -110,9 +97,9 @@ void APU::Master::power() {
}
void APU::Master::serialize(serializer &s) {
s.integer(left_enable);
s.integer(left_in_enable);
s.integer(left_volume);
s.integer(right_enable);
s.integer(right_in_enable);
s.integer(right_volume);
s.integer(channel4_left_enable);
s.integer(channel3_left_enable);

View File

@@ -1,8 +1,8 @@
struct Master {
bool left_enable;
unsigned left_volume;
bool right_enable;
unsigned right_volume;
bool left_in_enable;
uint3 left_volume;
bool right_in_enable;
uint3 right_volume;
bool channel4_left_enable;
bool channel3_left_enable;
bool channel2_left_enable;

View File

@@ -1,27 +1,32 @@
#ifdef APU_CPP
bool APU::Noise::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Noise::run() {
if(period && --period == 0) {
period = divisor << frequency;
if(frequency < 14) {
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
lfsr = (lfsr >> 1) ^ (bit << 14);
if(narrow_lfsr) lfsr |= (bit << 6);
lfsr = (lfsr >> 1) ^ (bit << (narrow_lfsr ? 6 : 14));
}
}
uint4 sample = (lfsr & 1) ? 0 : volume;
if(counter && length == 0) sample = 0;
uint4 sample = (lfsr & 1) ? (uint4)0 : volume;
if(enable == false) sample = 0;
output = (sample * 4369) - 32768;
output = sample;
}
void APU::Noise::clock_length() {
if(counter && length) length--;
if(counter && length) {
if(--length == 0) enable = false;
}
}
void APU::Noise::clock_envelope() {
if(envelope_period && --envelope_period == 0) {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
@@ -29,42 +34,42 @@ void APU::Noise::clock_envelope() {
}
void APU::Noise::write(unsigned r, uint8 data) {
if(r == 1) {
initial_length = 64 - (data & 0x3f);
length = initial_length;
if(r == 1) { //$ff20 NR41
length = 64 - (data & 0x3f);
}
if(r == 2) {
if(r == 2) { //$ff21 NR42
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) {
if(r == 3) { //$ff22 NR43
frequency = data >> 4;
narrow_lfsr = data & 0x08;
divisor = (data & 0x07) << 4;
if(divisor == 0) divisor = 8;
period = divisor << frequency;
}
if(r == 4) {
if(r == 4) { //$ff34 NR44
bool initialize = data & 0x80;
counter = data & 0x40;
if(initialize) {
enable = dac_enable();
lfsr = ~0U;
length = initial_length;
envelope_period = envelope_frequency;
volume = envelope_volume;
if(length == 0) length = 64;
}
}
}
void APU::Noise::power() {
initial_length = 0;
enable = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
@@ -82,7 +87,8 @@ void APU::Noise::power() {
}
void APU::Noise::serialize(serializer &s) {
s.integer(initial_length);
s.integer(enable);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);

View File

@@ -1,20 +1,23 @@
struct Noise {
unsigned initial_length;
unsigned envelope_volume;
bool enable;
uint4 envelope_volume;
bool envelope_direction;
unsigned envelope_frequency;
unsigned frequency;
uint3 envelope_frequency;
uint4 frequency;
bool narrow_lfsr;
unsigned divisor;
bool counter;
int16 output;
unsigned length;
unsigned envelope_period;
unsigned volume;
uint3 envelope_period;
uint4 volume;
unsigned period;
uint15 lfsr;
bool dac_enable();
void run();
void clock_length();
void clock_envelope();

View File

@@ -1,6 +1,8 @@
#ifdef APU_CPP
void APU::serialize(serializer &s) {
Processor::serialize(s);
s.array(mmio_data);
s.integer(sequencer_base);
s.integer(sequencer_step);

View File

@@ -1,9 +1,13 @@
#ifdef APU_CPP
bool APU::Square1::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Square1::run() {
if(period && --period == 0) {
period = 4 * (2048 - frequency);
phase = (phase + 1) & 7;
phase++;
switch(duty) {
case 0: duty_output = (phase == 6); break; //______-_
case 1: duty_output = (phase >= 6); break; //______--
@@ -12,45 +16,44 @@ void APU::Square1::run() {
}
}
uint4 sample = (duty_output ? volume : 0);
if(counter && length == 0) sample = 0;
uint4 sample = (duty_output ? volume : (uint4)0);
if(enable == false) sample = 0;
output = (sample * 4369) - 32768;
output = sample;
}
void APU::Square1::sweep() {
if(enable == false) return;
void APU::Square1::sweep(bool update) {
if(sweep_enable == false) return;
signed offset = frequency_shadow >> sweep_shift;
if(sweep_direction) offset = -offset;
frequency_shadow += offset;
sweep_negate = sweep_direction;
unsigned delta = frequency_shadow >> sweep_shift;
signed freq = frequency_shadow + (sweep_negate ? -delta : delta);
if(frequency_shadow < 0) {
frequency_shadow = 0;
} else if(frequency_shadow > 2047) {
frequency_shadow = 2048;
if(freq > 2047) {
enable = false;
}
if(frequency_shadow <= 2047 && sweep_shift) {
frequency = frequency_shadow;
} else if(sweep_shift && update) {
frequency_shadow = freq;
frequency = freq & 2047;
period = 4 * (2048 - frequency);
}
}
void APU::Square1::clock_length() {
if(counter && length) length--;
if(counter && length) {
if(--length == 0) enable = false;
}
}
void APU::Square1::clock_sweep() {
if(sweep_frequency && sweep_period && --sweep_period == 0) {
if(enable && sweep_frequency && --sweep_period == 0) {
sweep_period = sweep_frequency;
sweep();
sweep(1);
sweep(0);
}
}
void APU::Square1::clock_envelope() {
if(envelope_period && --envelope_period == 0) {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
@@ -58,39 +61,44 @@ void APU::Square1::clock_envelope() {
}
void APU::Square1::write(unsigned r, uint8 data) {
if(r == 0) {
if(r == 0) { //$ff10 NR10
if(sweep_negate && sweep_direction && !(data & 0x08)) enable = false;
sweep_frequency = (data >> 4) & 7;
sweep_direction = data & 0x08;
sweep_shift = data & 0x07;
}
if(r == 1) {
if(r == 1) { //$ff11 NR11
duty = data >> 6;
length = data & 0x3f;
length = 64 - (data & 0x3f);
}
if(r == 2) {
if(r == 2) { //$ff12 NR12
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) {
if(r == 3) { //$ff13 NR13
frequency = (frequency & 0x0700) | data;
}
if(r == 4) {
if(r == 4) { //$ff14 NR14
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable();
envelope_period = envelope_frequency;
volume = envelope_volume;
frequency_shadow = frequency;
sweep_period = sweep_frequency;
enable = sweep_period || sweep_shift;
if(sweep_shift) sweep();
sweep_enable = sweep_period || sweep_shift;
sweep_negate = false;
if(sweep_shift) sweep(0);
if(length == 0) length = 64;
}
}
@@ -98,9 +106,12 @@ void APU::Square1::write(unsigned r, uint8 data) {
}
void APU::Square1::power() {
enable = 0;
sweep_frequency = 0;
sweep_direction = 0;
sweep_shift = 0;
sweep_negate = 0;
duty = 0;
length = 0;
envelope_volume = 0;
@@ -116,14 +127,17 @@ void APU::Square1::power() {
envelope_period = 0;
sweep_period = 0;
frequency_shadow = 0;
enable = 0;
sweep_enable = 0;
volume = 0;
}
void APU::Square1::serialize(serializer &s) {
s.integer(enable);
s.integer(sweep_frequency);
s.integer(sweep_direction);
s.integer(sweep_shift);
s.integer(sweep_negate);
s.integer(duty);
s.integer(length);
s.integer(envelope_volume);
@@ -139,7 +153,7 @@ void APU::Square1::serialize(serializer &s) {
s.integer(envelope_period);
s.integer(sweep_period);
s.integer(frequency_shadow);
s.integer(enable);
s.integer(sweep_enable);
s.integer(volume);
}

View File

@@ -1,27 +1,32 @@
struct Square1 {
unsigned sweep_frequency;
unsigned sweep_direction;
unsigned sweep_shift;
unsigned duty;
bool enable;
uint3 sweep_frequency;
bool sweep_direction;
uint3 sweep_shift;
bool sweep_negate;
uint2 duty;
unsigned length;
unsigned envelope_volume;
unsigned envelope_direction;
unsigned envelope_frequency;
unsigned frequency;
unsigned counter;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint11 frequency;
bool counter;
int16 output;
bool duty_output;
unsigned phase;
uint3 phase;
unsigned period;
unsigned envelope_period;
unsigned sweep_period;
uint3 envelope_period;
uint3 sweep_period;
signed frequency_shadow;
bool enable;
unsigned volume;
bool sweep_enable;
uint4 volume;
bool dac_enable();
void run();
void sweep();
void sweep(bool update);
void clock_length();
void clock_sweep();
void clock_envelope();

View File

@@ -1,9 +1,13 @@
#ifdef APU_CPP
bool APU::Square2::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Square2::run() {
if(period && --period == 0) {
period = 4 * (2048 - frequency);
phase = (phase + 1) & 7;
phase++;
switch(duty) {
case 0: duty_output = (phase == 6); break; //______-_
case 1: duty_output = (phase >= 6); break; //______--
@@ -12,18 +16,20 @@ void APU::Square2::run() {
}
}
uint4 sample = (duty_output ? volume : 0);
if(counter && length == 0) sample = 0;
uint4 sample = (duty_output ? volume : (uint4)0);
if(enable == false) sample = 0;
output = (sample * 4369) - 32768;
output = sample;
}
void APU::Square2::clock_length() {
if(counter && length) length--;
if(counter && length) {
if(--length == 0) enable = false;
}
}
void APU::Square2::clock_envelope() {
if(envelope_period && --envelope_period == 0) {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
@@ -31,29 +37,32 @@ void APU::Square2::clock_envelope() {
}
void APU::Square2::write(unsigned r, uint8 data) {
if(r == 1) {
if(r == 1) { //$ff16 NR21
duty = data >> 6;
length = data & 0x3f;
length = 64 - (data & 0x3f);
}
if(r == 2) {
if(r == 2) { //$ff17 NR22
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) {
if(r == 3) { //$ff18 NR23
frequency = (frequency & 0x0700) | data;
}
if(r == 4) {
if(r == 4) { //$ff19 NR24
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable();
envelope_period = envelope_frequency;
volume = envelope_volume;
if(length == 0) length = 64;
}
}
@@ -61,6 +70,8 @@ void APU::Square2::write(unsigned r, uint8 data) {
}
void APU::Square2::power() {
enable = 0;
duty = 0;
length = 0;
envelope_volume = 0;
@@ -78,6 +89,8 @@ void APU::Square2::power() {
}
void APU::Square2::serialize(serializer &s) {
s.integer(enable);
s.integer(duty);
s.integer(length);
s.integer(envelope_volume);

View File

@@ -1,18 +1,22 @@
struct Square2 {
unsigned duty;
bool enable;
uint2 duty;
unsigned length;
unsigned envelope_volume;
unsigned envelope_direction;
unsigned envelope_frequency;
unsigned frequency;
unsigned counter;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint11 frequency;
bool counter;
int16 output;
bool duty_output;
unsigned phase;
uint3 phase;
unsigned period;
unsigned envelope_period;
unsigned volume;
uint3 envelope_period;
uint4 volume;
bool dac_enable();
void run();
void clock_length();

View File

@@ -3,57 +3,53 @@
void APU::Wave::run() {
if(period && --period == 0) {
period = 2 * (2048 - frequency);
pattern_offset = (pattern_offset + 1) & 31;
pattern_sample = pattern[pattern_offset];
pattern_sample = pattern[++pattern_offset];
}
uint4 sample = pattern_sample;
if(counter && length == 0) sample = 0;
uint4 sample = pattern_sample >> volume_shift;
if(enable == false) sample = 0;
output = (sample * 4369) - 32768;
output >>= volume;
output = sample;
}
void APU::Wave::clock_length() {
if(counter && length) length--;
if(counter && length) {
if(--length == 0) enable = false;
}
}
void APU::Wave::write(unsigned r, uint8 data) {
if(r == 0) {
if(r == 0) { //$ff1a NR30
dac_enable = data & 0x80;
if(dac_enable == false) enable = false;
}
if(r == 1) {
initial_length = 256 - data;
length = initial_length;
if(r == 1) { //$ff1b NR31
length = 256 - data;
}
if(r == 2) {
if(r == 2) { //$ff1c NR32
switch((data >> 5) & 3) {
case 0: volume = 16; break; // 0%
case 1: volume = 0; break; //100%
case 2: volume = 1; break; // 50%
case 3: volume = 2; break; // 25%
case 0: volume_shift = 4; break; // 0%
case 1: volume_shift = 0; break; //100%
case 2: volume_shift = 1; break; // 50%
case 3: volume_shift = 2; break; // 25%
}
}
if(r == 3) {
if(r == 3) { //$ff1d NR33
frequency = (frequency & 0x0700) | data;
}
if(r == 4) {
if(r == 4) { //$ff1e NR34
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize && dac_enable) {
enable = true;
if(initialize) {
enable = dac_enable;
pattern_offset = 0;
length = initial_length;
if(length == 0) length = 256;
}
}
@@ -67,17 +63,17 @@ void APU::Wave::write_pattern(unsigned p, uint8 data) {
}
void APU::Wave::power() {
enable = 0;
dac_enable = 0;
initial_length = 0;
volume = 0;
volume_shift = 0;
frequency = 0;
counter = 0;
random_lfsr r;
foreach(n, pattern) n = r() & 15;
for(auto &n : pattern) n = r() & 15;
output = 0;
enable = 0;
length = 0;
period = 0;
pattern_offset = 0;
@@ -85,15 +81,15 @@ void APU::Wave::power() {
}
void APU::Wave::serialize(serializer &s) {
s.integer(enable);
s.integer(dac_enable);
s.integer(initial_length);
s.integer(volume);
s.integer(volume_shift);
s.integer(frequency);
s.integer(counter);
s.array(pattern);
s.integer(output);
s.integer(enable);
s.integer(length);
s.integer(period);
s.integer(pattern_offset);

View File

@@ -1,17 +1,17 @@
struct Wave {
bool enable;
bool dac_enable;
unsigned initial_length;
unsigned volume;
unsigned frequency;
unsigned volume_shift;
uint11 frequency;
bool counter;
uint8 pattern[32];
int16 output;
bool enable;
unsigned length;
unsigned period;
unsigned pattern_offset;
unsigned pattern_sample;
uint5 pattern_offset;
uint4 pattern_sample;
void run();
void clock_length();

View File

@@ -16,14 +16,11 @@ namespace GameBoy {
#include "serialization.cpp"
Cartridge cartridge;
void Cartridge::load(const string &xml, const uint8_t *data, unsigned size) {
void Cartridge::load(System::Revision revision, const string &markup, const uint8_t *data, unsigned size) {
if(size == 0) size = 32768;
romdata = allocate<uint8>(romsize = size, 0xff);
if(data) memcpy(romdata, data, size);
//uint32_t crc = crc32_calculate(data, size);
//print("CRC32 = ", hex<4>(crc), "\n");
info.mapper = Mapper::Unknown;
info.ram = false;
info.battery = false;
@@ -33,42 +30,24 @@ void Cartridge::load(const string &xml, const uint8_t *data, unsigned size) {
info.romsize = 0;
info.ramsize = 0;
xml_element document = xml_parse(xml);
foreach(head, document.element) {
if(head.name == "cartridge") {
foreach(attr, head.attribute) {
if(attr.name == "mapper") {
if(attr.content == "none") info.mapper = Mapper::MBC0;
if(attr.content == "MBC1") info.mapper = Mapper::MBC1;
if(attr.content == "MBC2") info.mapper = Mapper::MBC2;
if(attr.content == "MBC3") info.mapper = Mapper::MBC3;
if(attr.content == "MBC5") info.mapper = Mapper::MBC5;
if(attr.content == "MMM01") info.mapper = Mapper::MMM01;
if(attr.content == "HuC1") info.mapper = Mapper::HuC1;
if(attr.content == "HuC3") info.mapper = Mapper::HuC3;
}
XML::Document document(markup);
if(attr.name == "rtc") info.rtc = (attr.content == "true" ? true : false);
if(attr.name == "rumble") info.rumble = (attr.content == "true" ? true : false);
}
auto &mapperid = document["cartridge"]["mapper"].data;
if(mapperid == "none" ) info.mapper = Mapper::MBC0;
if(mapperid == "MBC1" ) info.mapper = Mapper::MBC1;
if(mapperid == "MBC2" ) info.mapper = Mapper::MBC2;
if(mapperid == "MBC3" ) info.mapper = Mapper::MBC3;
if(mapperid == "MBC5" ) info.mapper = Mapper::MBC5;
if(mapperid == "MMM01") info.mapper = Mapper::MMM01;
if(mapperid == "HuC1" ) info.mapper = Mapper::HuC1;
if(mapperid == "HuC3" ) info.mapper = Mapper::HuC3;
foreach(elem, head.element) {
if(elem.name == "rom") {
foreach(attr, elem.attribute) {
if(attr.name == "size") info.romsize = hex(attr.content);
}
}
info.rtc = document["cartridge"]["rtc"].data == "true";
info.rumble = document["cartridge"]["rumble"].data == "true";
if(elem.name == "ram") {
info.ram = true;
foreach(attr, elem.attribute) {
if(attr.name == "size") info.ramsize = hex(attr.content);
if(attr.name == "battery") info.battery = (attr.content == "true" ? true : false);
}
}
}
}
}
info.romsize = numeral(document["cartridge"]["rom"]["size"].data);
info.ramsize = numeral(document["cartridge"]["ram"]["size"].data);
info.battery = document["cartridge"]["ram"]["battery"].data == "true";
switch(info.mapper) { default:
case Mapper::MBC0: mapper = &mbc0; break;
@@ -82,8 +61,10 @@ void Cartridge::load(const string &xml, const uint8_t *data, unsigned size) {
}
ramdata = new uint8_t[ramsize = info.ramsize]();
system.load();
system.load(revision);
loaded = true;
sha256 = nall::sha256(romdata, romsize);
}
void Cartridge::unload() {
@@ -117,12 +98,28 @@ void Cartridge::ram_write(unsigned addr, uint8 data) {
}
uint8 Cartridge::mmio_read(uint16 addr) {
if(bootrom_enable && within<0x0000, 0x00ff>(addr)) return System::BootROM::sgb[addr];
if(addr == 0xff50) return 0x00;
if(bootrom_enable) {
const uint8 *data = nullptr;
switch(system.revision()) { default:
case System::Revision::GameBoy: data = System::BootROM::dmg; break;
case System::Revision::SuperGameBoy: data = System::BootROM::sgb; break;
case System::Revision::GameBoyColor: data = System::BootROM::cgb; break;
}
if(addr >= 0x0000 && addr <= 0x00ff) return data[addr];
if(addr >= 0x0200 && addr <= 0x08ff && system.cgb()) return data[addr - 256];
}
return mapper->mmio_read(addr);
}
void Cartridge::mmio_write(uint16 addr, uint8 data) {
if(bootrom_enable && addr == 0xff50) bootrom_enable = false;
if(bootrom_enable && addr == 0xff50) {
bootrom_enable = false;
return;
}
mapper->mmio_write(addr, data);
}

View File

@@ -34,6 +34,7 @@ struct Cartridge : MMIO, property<Cartridge> {
} info;
readonly<bool> loaded;
readonly<string> sha256;
uint8_t *romdata;
unsigned romsize;
@@ -44,7 +45,7 @@ struct Cartridge : MMIO, property<Cartridge> {
MMIO *mapper;
bool bootrom_enable;
void load(const string &xml, const uint8_t *data, unsigned size);
void load(System::Revision revision, const string &markup, const uint8_t *data, unsigned size);
void unload();
uint8 rom_read(unsigned addr);

View File

@@ -1,53 +1,54 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::HuC1::mmio_read(uint16 addr) {
if(within<0x0000, 0x3fff>(addr)) {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom_read(addr);
}
if(within<0x4000, 0x7fff>(addr)) {
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff));
}
if(within<0xa000, 0xbfff>(addr)) {
if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
return 0x00;
if((addr & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
}
return 0x00;
}
void Cartridge::HuC1::mmio_write(uint16 addr, uint8 data) {
if(within<0x0000, 0x1fff>(addr)) {
ram_enable = (data & 0x0f) == 0x0a;
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram_writable = (data & 0x0f) == 0x0a;
return;
}
if(within<0x2000, 0x3fff>(addr)) {
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom_select = data;
if(rom_select == 0) rom_select = 1;
return;
}
if(within<0x4000, 0x5fff>(addr)) {
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram_select = data;
return;
}
if(within<0x6000, 0x7fff>(addr)) {
//unknown purpose
if((addr & 0xe000) == 0x6000) { //$6000-7fff
model = data & 0x01;
return;
}
if(within<0xa000, 0xbfff>(addr)) {
if(ram_enable) cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);
return;
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_writable == false) return;
return cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);
}
}
void Cartridge::HuC1::power() {
ram_enable = false;
ram_writable = false;
rom_select = 0x01;
ram_select = 0x00;
model = 0;
}
#endif

View File

@@ -1,7 +1,8 @@
struct HuC1 : MMIO {
bool ram_enable; //0000-1fff
uint8 rom_select; //2000-3fff
uint8 ram_select; //4000-5fff
bool ram_writable; //$0000-1fff
uint8 rom_select; //$2000-3fff
uint8 ram_select; //$4000-5fff
bool model; //$6000-7fff
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);

View File

@@ -1,15 +1,15 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::HuC3::mmio_read(uint16 addr) {
if(within<0x0000, 0x3fff>(addr)) {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom_read(addr);
}
if(within<0x4000, 0x7fff>(addr)) {
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff));
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
return 0x00;
}
@@ -18,27 +18,27 @@ uint8 Cartridge::HuC3::mmio_read(uint16 addr) {
}
void Cartridge::HuC3::mmio_write(uint16 addr, uint8 data) {
if(within<0x0000, 0x1fff>(addr)) {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram_enable = (data & 0x0f) == 0x0a;
return;
}
if(within<0x2000, 0x3fff>(addr)) {
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom_select = data;
return;
}
if(within<0x4000, 0x5fff>(addr)) {
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram_select = data;
return;
}
if(within<0x6000, 0x7fff>(addr)) {
if((addr & 0xe000) == 0x6000) { //$6000-7fff
//unknown purpose
return;
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);
return;
}

View File

@@ -1,7 +1,7 @@
struct HuC3 : MMIO {
bool ram_enable; //0000-1fff
uint8 rom_select; //2000-3fff
uint8 ram_select; //4000-5fff
bool ram_enable; //$0000-1fff
uint8 rom_select; //$2000-3fff
uint8 ram_select; //$4000-5fff
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);

View File

@@ -1,11 +1,11 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MBC0::mmio_read(uint16 addr) {
if(within<0x0000, 0x7fff>(addr)) {
if((addr & 0x8000) == 0x0000) { //$0000-7fff
return cartridge.rom_read(addr);
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram_read(addr & 0x1fff);
}
@@ -13,7 +13,7 @@ uint8 Cartridge::MBC0::mmio_read(uint16 addr) {
}
void Cartridge::MBC0::mmio_write(uint16 addr, uint8 data) {
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
cartridge.ram_write(addr & 0x1fff, data);
return;
}

View File

@@ -1,11 +1,11 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MBC1::mmio_read(uint16 addr) {
if(within<0x0000, 0x3fff>(addr)) {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom_read(addr);
}
if(within<0x4000, 0x7fff>(addr)) {
if((addr & 0xc000) == 0x4000) { //$4000-7fff
if(mode_select == 0) {
return cartridge.rom_read((ram_select << 19) | (rom_select << 14) | (addr & 0x3fff));
} else {
@@ -13,7 +13,7 @@ uint8 Cartridge::MBC1::mmio_read(uint16 addr) {
}
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) {
if(mode_select == 0) {
return cartridge.ram_read(addr & 0x1fff);
@@ -28,27 +28,27 @@ uint8 Cartridge::MBC1::mmio_read(uint16 addr) {
}
void Cartridge::MBC1::mmio_write(uint16 addr, uint8 data) {
if(within<0x0000, 0x1fff>(addr)) {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram_enable = (data & 0x0f) == 0x0a;
return;
}
if(within<0x2000, 0x3fff>(addr)) {
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom_select = (data & 0x1f) + ((data & 0x1f) == 0);
return;
}
if(within<0x4000, 0x5fff>(addr)) {
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram_select = data & 0x03;
return;
}
if(within<0x6000, 0x7fff>(addr)) {
if((addr & 0xe000) == 0x6000) { //$6000-7fff
mode_select = data & 0x01;
return;
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) {
if(mode_select == 0) {
cartridge.ram_write(addr & 0x1fff, data);

View File

@@ -1,8 +1,8 @@
struct MBC1 : MMIO {
bool ram_enable; //0000-1fff
uint8 rom_select; //2000-3fff
uint8 ram_select; //4000-5fff
bool mode_select; //6000-7fff
bool ram_enable; //$0000-1fff
uint8 rom_select; //$2000-3fff
uint8 ram_select; //$4000-5fff
bool mode_select; //$6000-7fff
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);

View File

@@ -1,15 +1,15 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MBC2::mmio_read(uint16 addr) {
if(within<0x0000, 0x3fff>(addr)) {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom_read(addr);
}
if(within<0x4000, 0x7fff>(addr)) {
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff));
}
if(within<0xa000, 0xa1ff>(addr)) {
if((addr & 0xee00) == 0xa000) { //$a000-a1ff
if(ram_enable) return cartridge.ram_read(addr & 0x1ff);
return 0x00;
}
@@ -18,17 +18,17 @@ uint8 Cartridge::MBC2::mmio_read(uint16 addr) {
}
void Cartridge::MBC2::mmio_write(uint16 addr, uint8 data) {
if(within<0x0000, 0x1fff>(addr)) {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
if(!(addr & 0x0100)) ram_enable = (data & 0x0f) == 0x0a;
return;
}
if(within<0x2000, 0x3fff>(addr)) {
if((addr & 0xe000) == 0x2000) { //$2000-3fff
if( (addr & 0x0100)) rom_select = (data & 0x0f) + ((data & 0x0f) == 0);
return;
}
if(within<0xa000, 0xa1ff>(addr)) {
if((addr & 0xee00) == 0xa000) { //$a000-a1ff
if(ram_enable) cartridge.ram_write(addr & 0x1ff, data & 0x0f);
return;
}

View File

@@ -1,6 +1,6 @@
struct MBC2 : MMIO {
bool ram_enable; //0000-1fff
uint8 rom_select; //2000-3fff
bool ram_enable; //$0000-1fff
uint8 rom_select; //$2000-3fff
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);

View File

@@ -19,15 +19,15 @@ void Cartridge::MBC3::second() {
}
uint8 Cartridge::MBC3::mmio_read(uint16 addr) {
if(within<0x0000, 0x3fff>(addr)) {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom_read(addr);
}
if(within<0x4000, 0x7fff>(addr)) {
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff));
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) {
if(ram_select >= 0x00 && ram_select <= 0x03) {
return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
@@ -45,22 +45,22 @@ uint8 Cartridge::MBC3::mmio_read(uint16 addr) {
}
void Cartridge::MBC3::mmio_write(uint16 addr, uint8 data) {
if(within<0x0000, 0x1fff>(addr)) {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram_enable = (data & 0x0f) == 0x0a;
return;
}
if(within<0x2000, 0x3fff>(addr)) {
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom_select = (data & 0x7f) + ((data & 0x7f) == 0);
return;
}
if(within<0x4000, 0x5fff>(addr)) {
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram_select = data;
return;
}
if(within<0x6000, 0x7fff>(addr)) {
if((addr & 0xe000) == 0x6000) { //$6000-7fff
if(rtc_latch == 0 && data == 1) {
rtc_latch_second = rtc_second;
rtc_latch_minute = rtc_minute;
@@ -72,7 +72,7 @@ void Cartridge::MBC3::mmio_write(uint16 addr, uint8 data) {
return;
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) {
if(ram_select >= 0x00 && ram_select <= 0x03) {
cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);

View File

@@ -1,8 +1,8 @@
struct MBC3 : MMIO {
bool ram_enable; //0000-1fff
uint8 rom_select; //2000-3fff
uint8 ram_select; //4000-5fff
bool rtc_latch; //6000-7fff
bool ram_enable; //$0000-1fff
uint8 rom_select; //$2000-3fff
uint8 ram_select; //$4000-5fff
bool rtc_latch; //$6000-7fff
bool rtc_halt;
unsigned rtc_second;

View File

@@ -1,15 +1,15 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MBC5::mmio_read(uint16 addr) {
if(within<0x0000, 0x3fff>(addr)) {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom_read(addr);
}
if(within<0x4000, 0x7fff>(addr)) {
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff));
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
return 0x00;
}
@@ -18,27 +18,27 @@ uint8 Cartridge::MBC5::mmio_read(uint16 addr) {
}
void Cartridge::MBC5::mmio_write(uint16 addr, uint8 data) {
if(within<0x0000, 0x1fff>(addr)) {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram_enable = (data & 0x0f) == 0x0a;
return;
}
if(within<0x2000, 0x2fff>(addr)) {
if((addr & 0xf000) == 0x2000) { //$2000-2fff
rom_select = (rom_select & 0x0100) | data;
return;
}
if(within<0x3000, 0x3fff>(addr)) {
if((addr & 0xf000) == 0x3000) { //$3000-3fff
rom_select = ((data & 1) << 8) | (rom_select & 0x00ff);
return;
}
if(within<0x4000, 0x5fff>(addr)) {
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram_select = data & 0x0f;
return;
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);
return;
}

View File

@@ -1,7 +1,7 @@
struct MBC5 : MMIO {
bool ram_enable; //0000-1fff
uint16 rom_select; //2000-2fff + 3000-3fff
uint8 ram_select; //4000-5fff
bool ram_enable; //$0000-1fff
uint16 rom_select; //$2000-2fff + $3000-3fff
uint8 ram_select; //$4000-5fff
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);

View File

@@ -1,19 +1,19 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MMM01::mmio_read(uint16 addr) {
if(within<0x0000, 0x7fff>(addr)) {
if((addr & 0x8000) == 0x0000) { //$0000-7fff
if(rom_mode == 0) return cartridge.rom_read(addr);
}
if(within<0x0000, 0x3fff>(addr)) {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom_read(0x8000 + (rom_base << 14) + (addr & 0x3fff));
}
if(within<0x4000, 0x7fff>(addr)) {
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom_read(0x8000 + (rom_base << 14) + (rom_select << 14) + (addr & 0x3fff));
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) return cartridge.ram_read((ram_select << 13) + (addr & 0x1fff));
return 0x00;
}
@@ -22,7 +22,7 @@ uint8 Cartridge::MMM01::mmio_read(uint16 addr) {
}
void Cartridge::MMM01::mmio_write(uint16 addr, uint8 data) {
if(within<0x0000, 0x1fff>(addr)) {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
if(rom_mode == 0) {
rom_mode = 1;
} else {
@@ -30,7 +30,7 @@ void Cartridge::MMM01::mmio_write(uint16 addr, uint8 data) {
}
}
if(within<0x2000, 0x3fff>(addr)) {
if((addr & 0xe000) == 0x2000) { //$2000-3fff
if(rom_mode == 0) {
rom_base = data & 0x3f;
} else {
@@ -38,24 +38,24 @@ void Cartridge::MMM01::mmio_write(uint16 addr, uint8 data) {
}
}
if(within<0x4000, 0x5fff>(addr)) {
if((addr & 0xe000) == 0x4000) { //$4000-5fff
if(rom_mode == 1) {
ram_select = data;
}
}
if(within<0x6000, 0x7fff>(addr)) {
if((addr & 0xe000) == 0x6000) { //$6000-7fff
//unknown purpose
}
if(within<0xa000, 0xbfff>(addr)) {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) cartridge.ram_write((ram_select << 13) + (addr & 0x1fff), data);
}
}
void Cartridge::MMM01::power() {
rom_mode = 0;
rom_base = 0x00;
rom_base = 0;
ram_enable = false;
rom_select = 0x01;

View File

@@ -41,9 +41,10 @@ void Cartridge::serialize(serializer &s) {
s.integer(mmm01.rom_select);
s.integer(mmm01.ram_select);
s.integer(huc1.ram_enable);
s.integer(huc1.ram_writable);
s.integer(huc1.rom_select);
s.integer(huc1.ram_select);
s.integer(huc1.model);
s.integer(huc3.ram_enable);
s.integer(huc3.rom_select);

91
bsnes/gameboy/cheat/cheat.cpp Executable file
View File

@@ -0,0 +1,91 @@
#include <gameboy/gameboy.hpp>
namespace GameBoy {
Cheat cheat;
bool Cheat::decode(const string &code_, unsigned &addr, unsigned &data, unsigned &comp) {
static bool initialize = false;
static uint8 mapProActionReplay[256], mapGameGenie[256];
if(initialize == false) {
initialize = true;
for(auto &n : mapProActionReplay) n = ~0;
mapProActionReplay['0'] = 0; mapProActionReplay['1'] = 1; mapProActionReplay['2'] = 2; mapProActionReplay['3'] = 3;
mapProActionReplay['4'] = 4; mapProActionReplay['5'] = 5; mapProActionReplay['6'] = 6; mapProActionReplay['7'] = 7;
mapProActionReplay['8'] = 8; mapProActionReplay['9'] = 9; mapProActionReplay['A'] = 10; mapProActionReplay['B'] = 11;
mapProActionReplay['C'] = 12; mapProActionReplay['D'] = 13; mapProActionReplay['E'] = 14; mapProActionReplay['F'] = 15;
for(auto &n : mapGameGenie) n = ~0;
mapGameGenie['0'] = 0; mapGameGenie['1'] = 1; mapGameGenie['2'] = 2; mapGameGenie['3'] = 3;
mapGameGenie['4'] = 4; mapGameGenie['5'] = 5; mapGameGenie['6'] = 6; mapGameGenie['7'] = 7;
mapGameGenie['8'] = 8; mapGameGenie['9'] = 9; mapGameGenie['A'] = 10; mapGameGenie['B'] = 11;
mapGameGenie['C'] = 12; mapGameGenie['D'] = 13; mapGameGenie['E'] = 14; mapGameGenie['F'] = 15;
}
string code = code_;
code.upper();
unsigned length = code.length(), bits = 0;
if(code.wildcard("????:??")) {
code = { substr(code, 0, 4), substr(code, 5, 2) };
for(unsigned n = 0; n < 6; n++) if(mapProActionReplay[code[n]] > 15) return false;
bits = hex(code);
addr = (bits >> 8) & 0xffff;
data = (bits >> 0) & 0xff;
comp = ~0;
return true;
}
if(code.wildcard("????:??:??")) {
code = { substr(code, 0, 4), substr(code, 5, 2), substr(code, 8, 2) };
for(unsigned n = 0; n < 8; n++) if(mapProActionReplay[code[n]] > 15) return false;
bits = hex(code);
addr = (bits >> 16) & 0xffff;
data = (bits >> 8) & 0xff;
comp = (bits >> 0) & 0xff;
return true;
}
if(code.wildcard("???" "-" "???")) {
code = { substr(code, 0, 3), substr(code, 4, 3) };
for(unsigned n = 0; n < 6; n++) if(mapGameGenie[code[n]] > 15) return false;
for(unsigned n = 0; n < 6; n++) bits |= mapGameGenie[code[n]] << (20 - n * 4);
addr = (bits >> 0) & 0xffff;
data = (bits >> 16) & 0xff;
comp = ~0;
addr = (((addr >> 4) | (addr << 12)) & 0xffff) ^ 0xf000;
return true;
}
if(code.wildcard("???" "-" "???" "-" "???")) {
code = { substr(code, 0, 3), substr(code, 4, 3), substr(code, 8, 1), substr(code, 10, 1) };
for(unsigned n = 0; n < 8; n++) if(mapGameGenie[code[n]] > 15) return false;
for(unsigned n = 0; n < 8; n++) bits |= mapGameGenie[code[n]] << (28 - n * 4);
addr = (bits >> 8) & 0xffff;
data = (bits >> 24) & 0xff;
comp = (bits >> 0) & 0xff;
addr = (((addr >> 4) | (addr << 12)) & 0xffff) ^ 0xf000;
comp = (((comp >> 2) | (comp << 6)) & 0xff) ^ 0xba;
return true;
}
return false;
}
void Cheat::synchronize() {
for(auto &n : override) n = false;
for(unsigned n = 0; n < size(); n++) {
override[operator[](n).addr] = true;
}
}
}

14
bsnes/gameboy/cheat/cheat.hpp Executable file
View File

@@ -0,0 +1,14 @@
struct CheatCode {
unsigned addr;
unsigned data;
unsigned comp;
};
struct Cheat : public linear_vector<CheatCode> {
static bool decode(const string &code, unsigned &addr, unsigned &data, unsigned &comp);
void synchronize();
bool override[65536];
};
extern Cheat cheat;

View File

@@ -571,6 +571,13 @@ void CPU::op_halt() {
}
void CPU::op_stop() {
if(status.speed_switch) {
status.speed_switch = 0;
status.speed_double ^= 1;
frequency = 4 * 1024 * 1024;
if(status.speed_double) frequency *= 2;
return;
}
status.stop = true;
while(status.stop == true) op_io();
}
@@ -580,7 +587,8 @@ void CPU::op_di() {
}
void CPU::op_ei() {
status.ime = 1;
status.ei = true;
//status.ime = 1;
}
//jump commands

View File

@@ -94,15 +94,43 @@ void CPU::interrupt_exec(uint16 pc) {
}
void CPU::power() {
create(Main, 4194304);
create(Main, 4 * 1024 * 1024);
for(unsigned n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM
for(unsigned n = 0xe000; n <= 0xfdff; n++) bus.mmio[n] = this; //WRAM (mirror)
for(unsigned n = 0xff00; n <= 0xff0f; n++) bus.mmio[n] = this; //MMIO
for(unsigned n = 0xff80; n <= 0xffff; n++) bus.mmio[n] = this; //HRAM+IE
for(unsigned n = 0xff80; n <= 0xfffe; n++) bus.mmio[n] = this; //HRAM
for(unsigned n = 0; n < 8192; n++) wram[n] = 0x00;
for(unsigned n = 0; n < 128; n++) hram[n] = 0x00;
bus.mmio[0xff00] = this; //JOYP
bus.mmio[0xff01] = this; //SB
bus.mmio[0xff02] = this; //SC
bus.mmio[0xff04] = this; //DIV
bus.mmio[0xff05] = this; //TIMA
bus.mmio[0xff06] = this; //TMA
bus.mmio[0xff07] = this; //TAC
bus.mmio[0xff0f] = this; //IF
bus.mmio[0xff46] = this; //DMA
bus.mmio[0xffff] = this; //IE
if(system.cgb()) {
bus.mmio[0xff4d] = this; //KEY1
bus.mmio[0xff51] = this; //HDMA1
bus.mmio[0xff52] = this; //HDMA2
bus.mmio[0xff53] = this; //HDMA3
bus.mmio[0xff54] = this; //HDMA4
bus.mmio[0xff55] = this; //HDMA5
bus.mmio[0xff56] = this; //RP
bus.mmio[0xff6c] = this; //???
bus.mmio[0xff70] = this; //SVBK
bus.mmio[0xff72] = this; //???
bus.mmio[0xff73] = this; //???
bus.mmio[0xff74] = this; //???
bus.mmio[0xff75] = this; //???
bus.mmio[0xff76] = this; //???
bus.mmio[0xff77] = this; //???
}
for(auto &n : wram) n = 0x00;
for(auto &n : hram) n = 0x00;
r[PC] = 0x0000;
r[SP] = 0x0000;
@@ -114,6 +142,7 @@ void CPU::power() {
status.clock = 0;
status.halt = false;
status.stop = false;
status.ei = false;
status.ime = 0;
status.p15 = 0;
@@ -142,6 +171,23 @@ void CPU::power() {
status.interrupt_request_stat = 0;
status.interrupt_request_vblank = 0;
status.speed_double = 0;
status.speed_switch = 0;
status.dma_source = 0;
status.dma_target = 0;
status.dma_mode = 0;
status.dma_length = 0;
status.ff6c = 0;
status.ff72 = 0;
status.ff73 = 0;
status.ff74 = 0;
status.ff75 = 0;
status.wram_bank = 1;
status.interrupt_enable_joypad = 0;
status.interrupt_enable_serial = 0;
status.interrupt_enable_timer = 0;

View File

@@ -17,6 +17,7 @@ struct CPU : Processor, MMIO {
unsigned clock;
bool halt;
bool stop;
bool ei;
bool ime;
//$ff00 JOYP
@@ -53,6 +54,32 @@ struct CPU : Processor, MMIO {
bool interrupt_request_stat;
bool interrupt_request_vblank;
//$ff4d KEY1
bool speed_double;
bool speed_switch;
//$ff51,$ff52 HDMA1,HDMA2
uint16 dma_source;
//$ff53,$ff54 HDMA3,HDMA4
uint16 dma_target;
//$ff55 HDMA5
bool dma_mode;
uint16 dma_length;
//$ff6c ???
uint8 ff6c;
//$ff70 SVBK
uint3 wram_bank;
//$ff72-$ff75 ???
uint8 ff72;
uint8 ff73;
uint8 ff74;
uint8 ff75;
//$ffff IE
bool interrupt_enable_joypad;
bool interrupt_enable_serial;
@@ -61,7 +88,7 @@ struct CPU : Processor, MMIO {
bool interrupt_enable_vblank;
} status;
uint8 wram[8192];
uint8 wram[32768]; //GB=8192, GBC=32768
uint8 hram[128];
static void Main();

View File

@@ -1,17 +1,24 @@
#ifdef CPU_CPP
unsigned CPU::wram_addr(uint16 addr) const {
addr &= 0x1fff;
if(addr < 0x1000) return addr;
auto bank = status.wram_bank + (status.wram_bank == 0);
return (bank * 0x1000) + (addr & 0x0fff);
}
void CPU::mmio_joyp_poll() {
unsigned button = 0, dpad = 0;
button |= system.interface->input_poll((unsigned)Input::Start) << 3;
button |= system.interface->input_poll((unsigned)Input::Select) << 2;
button |= system.interface->input_poll((unsigned)Input::B) << 1;
button |= system.interface->input_poll((unsigned)Input::A) << 0;
button |= interface->inputPoll((unsigned)Input::Start) << 3;
button |= interface->inputPoll((unsigned)Input::Select) << 2;
button |= interface->inputPoll((unsigned)Input::B) << 1;
button |= interface->inputPoll((unsigned)Input::A) << 0;
dpad |= system.interface->input_poll((unsigned)Input::Down) << 3;
dpad |= system.interface->input_poll((unsigned)Input::Up) << 2;
dpad |= system.interface->input_poll((unsigned)Input::Left) << 1;
dpad |= system.interface->input_poll((unsigned)Input::Right) << 0;
dpad |= interface->inputPoll((unsigned)Input::Down) << 3;
dpad |= interface->inputPoll((unsigned)Input::Up) << 2;
dpad |= interface->inputPoll((unsigned)Input::Left) << 1;
dpad |= interface->inputPoll((unsigned)Input::Right) << 0;
status.joyp = 0x0f;
if(status.p15 == 1 && status.p14 == 1) status.joyp -= status.mlt_req;
@@ -21,8 +28,7 @@ void CPU::mmio_joyp_poll() {
}
uint8 CPU::mmio_read(uint16 addr) {
if(addr >= 0xc000 && addr <= 0xdfff) return wram[addr & 0x1fff];
if(addr >= 0xe000 && addr <= 0xfdff) return wram[addr & 0x1fff];
if(addr >= 0xc000 && addr <= 0xfdff) return wram[wram_addr(addr)];
if(addr >= 0xff80 && addr <= 0xfffe) return hram[addr & 0x7f];
if(addr == 0xff00) { //JOYP
@@ -65,6 +71,50 @@ uint8 CPU::mmio_read(uint16 addr) {
| (status.interrupt_request_vblank << 0);
}
if(addr == 0xff4d) { //KEY1
return (status.speed_double << 7);
}
if(addr == 0xff55) { //HDMA5
return (status.dma_length / 16) - 1;
}
if(addr == 0xff56) { //RP
return 0x02;
}
if(addr == 0xff6c) { //???
return 0xfe | status.ff6c;
}
if(addr == 0xff70) { //SVBK
return status.wram_bank;
}
if(addr == 0xff72) { //???
return status.ff72;
}
if(addr == 0xff73) { //???
return status.ff73;
}
if(addr == 0xff74) { //???
return status.ff74;
}
if(addr == 0xff75) { //???
return 0x8f | status.ff75;
}
if(addr == 0xff76) { //???
return 0x00;
}
if(addr == 0xff77) { //???
return 0x00;
}
if(addr == 0xffff) { //IE
return (status.interrupt_enable_joypad << 4)
| (status.interrupt_enable_serial << 3)
@@ -77,14 +127,13 @@ uint8 CPU::mmio_read(uint16 addr) {
}
void CPU::mmio_write(uint16 addr, uint8 data) {
if(addr >= 0xc000 && addr <= 0xdfff) { wram[addr & 0x1fff] = data; return; }
if(addr >= 0xe000 && addr <= 0xfdff) { wram[addr & 0x1fff] = data; return; }
if(addr >= 0xc000 && addr <= 0xfdff) { wram[wram_addr(addr)] = data; return; }
if(addr >= 0xff80 && addr <= 0xfffe) { hram[addr & 0x7f] = data; return; }
if(addr == 0xff00) { //JOYP
status.p15 = data & 0x20;
status.p14 = data & 0x10;
system.interface->joyp_write(status.p15, status.p14);
interface->joypWrite(status.p15, status.p14);
mmio_joyp_poll();
return;
}
@@ -131,6 +180,84 @@ void CPU::mmio_write(uint16 addr, uint8 data) {
return;
}
if(addr == 0xff46) { //DMA
for(unsigned n = 0x00; n <= 0x9f; n++) {
bus.write(0xfe00 + n, bus.read((data << 8) + n));
add_clocks(4);
}
return;
}
if(addr == 0xff4d) { //KEY1
status.speed_switch = data & 0x01;
return;
}
if(addr == 0xff51) { //HDMA1
status.dma_source = (status.dma_source & 0x00ff) | (data << 8);
return;
}
if(addr == 0xff52) { //HDMA2
status.dma_source = (status.dma_source & 0xff00) | (data << 0);
return;
}
if(addr == 0xff53) { //HDMA3
status.dma_target = (status.dma_target & 0x00ff) | (data << 8);
return;
}
if(addr == 0xff54) { //HDMA4
status.dma_target = (status.dma_target & 0xff00) | (data << 0);
return;
}
if(addr == 0xff55) { //HDMA5
status.dma_mode = data & 0x80;
status.dma_length = ((data & 0x7f) + 1) * 16;
if(status.dma_mode == 0) do {
bus.write(status.dma_target++, bus.read(status.dma_source++));
add_clocks(4 << status.speed_double);
} while(--status.dma_length);
return;
}
if(addr == 0xff56) { //RP
return;
}
if(addr == 0xff6c) { //???
status.ff6c = data & 0x01;
return;
}
if(addr == 0xff72) { //???
status.ff72 = data;
return;
}
if(addr == 0xff73) { //???
status.ff73 = data;
return;
}
if(addr == 0xff74) { //???
status.ff74 = data;
return;
}
if(addr == 0xff75) { //???
status.ff75 = data & 0x70;
return;
}
if(addr == 0xff70) { //SVBK
status.wram_bank = data & 0x07;
return;
}
if(addr == 0xffff) { //IE
status.interrupt_enable_joypad = data & 0x10;
status.interrupt_enable_serial = data & 0x08;

View File

@@ -1,3 +1,4 @@
unsigned wram_addr(uint16 addr) const;
void mmio_joyp_poll();
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);

View File

@@ -1,6 +1,8 @@
#ifdef CPU_CPP
void CPU::serialize(serializer &s) {
Processor::serialize(s);
s.array(wram);
s.array(hram);
@@ -21,6 +23,7 @@ void CPU::serialize(serializer &s) {
s.integer(status.clock);
s.integer(status.halt);
s.integer(status.stop);
s.integer(status.ei);
s.integer(status.ime);
s.integer(status.p15);
@@ -46,6 +49,23 @@ void CPU::serialize(serializer &s) {
s.integer(status.interrupt_request_stat);
s.integer(status.interrupt_request_vblank);
s.integer(status.speed_double);
s.integer(status.speed_switch);
s.integer(status.dma_source);
s.integer(status.dma_target);
s.integer(status.dma_mode);
s.integer(status.dma_length);
s.integer(status.ff6c);
s.integer(status.wram_bank);
s.integer(status.ff72);
s.integer(status.ff73);
s.integer(status.ff74);
s.integer(status.ff75);
s.integer(status.interrupt_enable_joypad);
s.integer(status.interrupt_enable_serial);
s.integer(status.interrupt_enable_timer);

View File

@@ -1,18 +1,28 @@
#ifdef CPU_CPP
void CPU::op_io() {
cycle_edge();
add_clocks(4);
}
uint8 CPU::op_read(uint16 addr) {
cycle_edge();
uint8 r = bus.read(addr);
add_clocks(4);
return r;
}
void CPU::op_write(uint16 addr, uint8 data) {
cycle_edge();
bus.write(addr, data);
add_clocks(4);
}
void CPU::cycle_edge() {
if(status.ei) {
status.ei = false;
status.ime = 1;
}
}
#endif

View File

@@ -1,5 +1,3 @@
//4194304hz (4 * 1024 * 1024)
//70224 clocks/frame
// 456 clocks/scanline
// 154 scanlines/frame
@@ -10,25 +8,25 @@
void CPU::add_clocks(unsigned clocks) {
system.clocks_executed += clocks;
scheduler.exit(Scheduler::ExitReason::StepEvent);
if(system.sgb()) scheduler.exit(Scheduler::ExitReason::StepEvent);
status.clock += clocks;
if(status.clock >= 4194304) {
status.clock -= 4194304;
if(status.clock >= 4 * 1024 * 1024) {
status.clock -= 4 * 1024 * 1024;
cartridge.mbc3.second();
}
//4194304 / N(hz) - 1 = mask
//4MHz / N(hz) - 1 = mask
if((status.clock & 15) == 0) timer_262144hz();
if((status.clock & 63) == 0) timer_65536hz();
if((status.clock & 255) == 0) timer_16384hz();
if((status.clock & 511) == 0) timer_8192hz();
if((status.clock & 1023) == 0) timer_4096hz();
lcd.clock -= clocks;
lcd.clock -= clocks * lcd.frequency;
if(lcd.clock <= 0) co_switch(scheduler.active_thread = lcd.thread);
apu.clock -= clocks;
apu.clock -= clocks * apu.frequency;
if(apu.clock <= 0) co_switch(scheduler.active_thread = apu.thread);
}
@@ -79,4 +77,14 @@ void CPU::timer_4096hz() {
}
}
void CPU::hblank() {
if(status.dma_mode == 1 && status.dma_length) {
for(unsigned n = 0; n < 16; n++) {
bus.write(status.dma_target++, bus.read(status.dma_source++));
add_clocks(4);
}
status.dma_length -= 16;
}
}
#endif

View File

@@ -4,8 +4,10 @@ void timer_65536hz();
void timer_16384hz();
void timer_8192hz();
void timer_4096hz();
void hblank();
//opcode.cpp
void op_io();
uint8 op_read(uint16 addr);
void op_write(uint16 addr, uint8 data);
void cycle_edge();

View File

@@ -1,89 +1,49 @@
//bgameboy
//author: byuu
//project started: 2010-12-27
#ifndef GAMEBOY_HPP
#define GAMEBOY_HPP
#include <base/base.hpp>
namespace GameBoy {
namespace Info {
static const char Name[] = "bgameboy";
static const char Version[] = "000.19";
static unsigned SerializerVersion = 1;
static const unsigned SerializerVersion = 3;
}
}
#include <libco/libco.h>
/*
bgameboy - Game Boy, Super Game Boy, and Game Boy Color emulator
author: byuu
license: GPLv3
project started: 2010-12-27
*/
#include <nall/foreach.hpp>
#include <nall/platform.hpp>
#include <nall/property.hpp>
#include <nall/random.hpp>
#include <nall/serializer.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
#include <nall/varint.hpp>
using namespace nall;
#include <libco/libco.h>
#include <nall/gameboy/cartridge.hpp>
namespace GameBoy {
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;
typedef uint_t< 1> uint1;
typedef uint_t< 2> uint2;
typedef uint_t< 3> uint3;
typedef uint_t< 4> uint4;
typedef uint_t< 5> uint5;
typedef uint_t< 6> uint6;
typedef uint_t< 7> uint7;
typedef uint_t< 9> uint9;
typedef uint_t<10> uint10;
typedef uint_t<11> uint11;
typedef uint_t<12> uint12;
typedef uint_t<13> uint13;
typedef uint_t<14> uint14;
typedef uint_t<15> uint15;
typedef uint_t<17> uint17;
typedef uint_t<18> uint18;
typedef uint_t<19> uint19;
typedef uint_t<20> uint20;
typedef uint_t<21> uint21;
typedef uint_t<22> uint22;
typedef uint_t<23> uint23;
typedef uint_t<24> uint24;
typedef uint_t<25> uint25;
typedef uint_t<26> uint26;
typedef uint_t<27> uint27;
typedef uint_t<28> uint28;
typedef uint_t<29> uint29;
typedef uint_t<30> uint30;
typedef uint_t<31> uint31;
template<uint16 lo, uint16 hi>
alwaysinline bool within(uint16 addr) {
static const uint16 mask = ~(hi ^ lo);
return (addr & mask) == lo;
}
struct Processor {
cothread_t thread;
unsigned frequency;
int64 clock;
inline void create(void (*entrypoint_)(), unsigned frequency_) {
inline void create(void (*entrypoint)(), unsigned frequency) {
if(thread) co_delete(thread);
thread = co_create(65536 * sizeof(void*), entrypoint_);
frequency = frequency_;
thread = co_create(65536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
}
inline Processor() : thread(0) {}
inline void serialize(serializer &s) {
s.integer(frequency);
s.integer(clock);
}
inline Processor() : thread(nullptr) {
}
inline ~Processor() {
if(thread) co_delete(thread);
}
};
#include <gameboy/memory/memory.hpp>
@@ -93,4 +53,8 @@ namespace GameBoy {
#include <gameboy/cpu/cpu.hpp>
#include <gameboy/apu/apu.hpp>
#include <gameboy/lcd/lcd.hpp>
#include <gameboy/cheat/cheat.hpp>
#include <gameboy/video/video.hpp>
};
#endif

View File

@@ -0,0 +1,27 @@
#include <gameboy/gameboy.hpp>
namespace GameBoy {
Interface *interface = nullptr;
void Interface::lcdScanline() {
}
void Interface::joypWrite(bool p15, bool p14) {
}
void Interface::videoRefresh(const uint16_t *data) {
}
void Interface::audioSample(int16_t center, int16_t left, int16_t right) {
}
bool Interface::inputPoll(unsigned id) {
return false;
}
void Interface::message(const string &text) {
print(text, "\n");
}
}

View File

@@ -1,12 +1,12 @@
class Interface {
public:
virtual void lcd_scanline() {}
virtual void joyp_write(bool p15, bool p14) {}
struct Interface {
virtual void lcdScanline();
virtual void joypWrite(bool p15, bool p14);
virtual void video_refresh(const uint8_t *data) {}
virtual void audio_sample(int16_t center, int16_t left, int16_t right) {}
virtual void input_poll() {}
virtual bool input_poll(unsigned id) {}
virtual void videoRefresh(const uint16_t *data);
virtual void audioSample(int16_t center, int16_t left, int16_t right);
virtual bool inputPoll(unsigned id);
virtual void message(const string &text) { print(text, "\n"); }
virtual void message(const string &text);
};
extern Interface *interface;

185
bsnes/gameboy/lcd/cgb.cpp Executable file
View File

@@ -0,0 +1,185 @@
#ifdef LCD_CPP
void LCD::cgb_render() {
for(unsigned n = 0; n < 160; n++) {
line[n] = 0x7fff;
origin[n] = Origin::None;
}
if(status.display_enable) {
cgb_render_bg();
if(status.window_display_enable) cgb_render_window();
if(status.ob_enable) cgb_render_ob();
}
uint16 *output = screen + status.ly * 160;
for(unsigned n = 0; n < 160; n++) output[n] = line[n];
interface->lcdScanline();
}
//Attributes:
//0x80: 0 = OAM priority, 1 = BG priority
//0x40: vertical flip
//0x20: horizontal flip
//0x08: VRAM bank#
//0x07: palette#
void LCD::cgb_read_tile(bool select, unsigned x, unsigned y, unsigned &tile, unsigned &attr, unsigned &data) {
unsigned tmaddr = 0x1800 + (select << 10);
tmaddr += (((y >> 3) << 5) + (x >> 3)) & 0x03ff;
tile = vram[0x0000 + tmaddr];
attr = vram[0x2000 + tmaddr];
unsigned tdaddr = attr & 0x08 ? 0x2000 : 0x0000;
if(status.bg_tiledata_select == 0) {
tdaddr += 0x1000 + ((int8)tile << 4);
} else {
tdaddr += 0x0000 + (tile << 4);
}
y &= 7;
if(attr & 0x40) y ^= 7;
tdaddr += y << 1;
data = vram[tdaddr++] << 0;
data |= vram[tdaddr++] << 8;
if(attr & 0x20) data = hflip(data);
}
void LCD::cgb_render_bg() {
unsigned iy = (status.ly + status.scy) & 255;
unsigned ix = status.scx, tx = ix & 7;
unsigned tile, attr, data;
cgb_read_tile(status.bg_tilemap_select, ix, iy, tile, attr, data);
for(unsigned ox = 0; ox < 160; ox++) {
unsigned index = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
unsigned palette_index = ((attr & 0x07) << 3) + (index << 1);
unsigned palette = 0;
palette |= bgpd[palette_index++] << 0;
palette |= bgpd[palette_index++] << 8;
palette &= 0x7fff;
line[ox] = palette;
origin[ox] = (attr & 0x80 ? Origin::BGP : Origin::BG);
ix = (ix + 1) & 255;
tx = (tx + 1) & 7;
if(tx == 0) cgb_read_tile(status.bg_tilemap_select, ix, iy, tile, attr, data);
}
}
void LCD::cgb_render_window() {
if(status.ly - status.wy >= 144u) return;
if(status.wx >= 167u) return;
unsigned iy = status.wyc++;
unsigned ix = (7 - status.wx) & 255, tx = ix & 7;
unsigned tile, attr, data;
cgb_read_tile(status.window_tilemap_select, ix, iy, tile, attr, data);
for(unsigned ox = 0; ox < 160; ox++) {
unsigned index = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
unsigned palette_index = ((attr & 0x07) << 3) + (index << 1);
unsigned palette = 0;
palette |= bgpd[palette_index++] << 0;
palette |= bgpd[palette_index++] << 8;
palette &= 0x7fff;
if(ox - (status.wx - 7) < 160u) {
line[ox] = palette;
origin[ox] = (attr & 0x80 ? Origin::BGP : Origin::BG);
}
ix = (ix + 1) & 255;
tx = (tx + 1) & 7;
if(tx == 0) cgb_read_tile(status.window_tilemap_select, ix, iy, tile, attr, data);
}
}
//Attributes:
//0x80: 0 = OBJ above BG, 1 = BG above OBJ
//0x40: vertical flip
//0x20: horizontal flip
//0x08: VRAM bank#
//0x07: palette#
void LCD::cgb_render_ob() {
const unsigned Height = (status.ob_size == 0 ? 8 : 16);
unsigned sprite[10], sprites = 0;
//find first ten sprites on this scanline
for(unsigned s = 0; s < 40; s++) {
unsigned sy = oam[(s << 2) + 0] - 16;
unsigned sx = oam[(s << 2) + 1] - 8;
sy = status.ly - sy;
if(sy >= Height) continue;
sprite[sprites++] = s;
if(sprites == 10) break;
}
//sort by X-coordinate, when equal, lower address comes first
for(unsigned x = 0; x < sprites; x++) {
for(unsigned y = x + 1; y < sprites; y++) {
signed sx = oam[(sprite[x] << 2) + 1] - 8;
signed sy = oam[(sprite[y] << 2) + 1] - 8;
if(sy < sx) {
sprite[x] ^= sprite[y];
sprite[y] ^= sprite[x];
sprite[x] ^= sprite[y];
}
}
}
//render backwards, so that first sprite has highest priority
for(signed s = sprites - 1; s >= 0; s--) {
unsigned n = sprite[s] << 2;
unsigned sy = oam[n + 0] - 16;
unsigned sx = oam[n + 1] - 8;
unsigned tile = oam[n + 2] & ~status.ob_size;
unsigned attr = oam[n + 3];
sy = status.ly - sy;
if(sy >= Height) continue;
if(attr & 0x40) sy ^= (Height - 1);
unsigned tdaddr = (attr & 0x08 ? 0x2000 : 0x0000) + (tile << 4) + (sy << 1), data = 0;
data |= vram[tdaddr++] << 0;
data |= vram[tdaddr++] << 8;
if(attr & 0x20) data = hflip(data);
for(unsigned tx = 0; tx < 8; tx++) {
unsigned index = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
if(index == 0) continue;
unsigned palette_index = ((attr & 0x07) << 3) + (index << 1);
unsigned palette = 0;
palette |= obpd[palette_index++] << 0;
palette |= obpd[palette_index++] << 8;
palette &= 0x7fff;
unsigned ox = sx + tx;
if(ox < 160) {
//When LCDC.D0 (BG enable) is off, OB is always rendered above BG+Window
if(status.bg_enable) {
if(origin[ox] == Origin::BGP) continue;
if(attr & 0x80) {
if(origin[ox] == Origin::BG || origin[ox] == Origin::BGP) {
if(line[ox] > 0) continue;
}
}
}
line[ox] = palette;
origin[ox] = Origin::OB;
}
}
}
}
#endif

145
bsnes/gameboy/lcd/dmg.cpp Executable file
View File

@@ -0,0 +1,145 @@
#ifdef LCD_CPP
void LCD::dmg_render() {
for(unsigned n = 0; n < 160; n++) {
line[n] = 0x00;
origin[n] = Origin::None;
}
if(status.display_enable) {
if(status.bg_enable) dmg_render_bg();
if(status.window_display_enable) dmg_render_window();
if(status.ob_enable) dmg_render_ob();
}
uint16 *output = screen + status.ly * 160;
for(unsigned n = 0; n < 160; n++) output[n] = line[n];
interface->lcdScanline();
}
uint16 LCD::dmg_read_tile(bool select, unsigned x, unsigned y) {
unsigned tmaddr = 0x1800 + (select << 10), tdaddr;
tmaddr += (((y >> 3) << 5) + (x >> 3)) & 0x03ff;
if(status.bg_tiledata_select == 0) {
tdaddr = 0x1000 + ((int8)vram[tmaddr] << 4);
} else {
tdaddr = 0x0000 + (vram[tmaddr] << 4);
}
tdaddr += (y & 7) << 1;
return (vram[tdaddr + 0] << 0) | (vram[tdaddr + 1] << 8);
}
void LCD::dmg_render_bg() {
unsigned iy = (status.ly + status.scy) & 255;
unsigned ix = status.scx, tx = ix & 7;
unsigned data = dmg_read_tile(status.bg_tilemap_select, ix, iy);
for(unsigned ox = 0; ox < 160; ox++) {
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
line[ox] = bgp[palette];
origin[ox] = Origin::BG;
ix = (ix + 1) & 255;
tx = (tx + 1) & 7;
if(tx == 0) data = dmg_read_tile(status.bg_tilemap_select, ix, iy);
}
}
void LCD::dmg_render_window() {
if(status.ly - status.wy >= 144u) return;
if(status.wx >= 167u) return;
unsigned iy = status.wyc++;
unsigned ix = (7 - status.wx) & 255, tx = ix & 7;
unsigned data = dmg_read_tile(status.window_tilemap_select, ix, iy);
for(unsigned ox = 0; ox < 160; ox++) {
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
if(ox - (status.wx - 7) < 160u) {
line[ox] = bgp[palette];
origin[ox] = Origin::BG;
}
ix = (ix + 1) & 255;
tx = (tx + 1) & 7;
if(tx == 0) data = dmg_read_tile(status.window_tilemap_select, ix, iy);
}
}
//Attributes:
//0x80: 0 = OBJ above BG, 1 = BG above OBJ
//0x40: vertical flip
//0x20: horizontal flip
//0x10: palette#
void LCD::dmg_render_ob() {
const unsigned Height = (status.ob_size == 0 ? 8 : 16);
unsigned sprite[10], sprites = 0;
//find first ten sprites on this scanline
for(unsigned s = 0; s < 40; s++) {
unsigned sy = oam[(s << 2) + 0] - 16;
unsigned sx = oam[(s << 2) + 1] - 8;
sy = status.ly - sy;
if(sy >= Height) continue;
sprite[sprites++] = s;
if(sprites == 10) break;
}
//sort by X-coordinate, when equal, lower address comes first
for(unsigned x = 0; x < sprites; x++) {
for(unsigned y = x + 1; y < sprites; y++) {
signed sx = oam[(sprite[x] << 2) + 1] - 8;
signed sy = oam[(sprite[y] << 2) + 1] - 8;
if(sy < sx) {
sprite[x] ^= sprite[y];
sprite[y] ^= sprite[x];
sprite[x] ^= sprite[y];
}
}
}
//render backwards, so that first sprite has highest priority
for(signed s = sprites - 1; s >= 0; s--) {
unsigned n = sprite[s] << 2;
unsigned sy = oam[n + 0] - 16;
unsigned sx = oam[n + 1] - 8;
unsigned tile = oam[n + 2] & ~status.ob_size;
unsigned attr = oam[n + 3];
sy = status.ly - sy;
if(sy >= Height) continue;
if(attr & 0x40) sy ^= (Height - 1);
unsigned tdaddr = (tile << 4) + (sy << 1), data = 0;
data |= vram[tdaddr++] << 0;
data |= vram[tdaddr++] << 8;
if(attr & 0x20) data = hflip(data);
for(unsigned tx = 0; tx < 8; tx++) {
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
if(palette == 0) continue;
palette = obp[(bool)(attr & 0x10)][palette];
unsigned ox = sx + tx;
if(ox < 160) {
if(attr & 0x80) {
if(origin[ox] == Origin::BG) {
if(line[ox] > 0) continue;
}
}
line[ox] = palette;
origin[ox] = Origin::OB;
}
}
}
}
#endif

View File

@@ -1,8 +1,16 @@
#include <gameboy/gameboy.hpp>
//LY = 0-153
//Raster = 0-143
//Vblank = 144-153
//LX = 0-455
#define LCD_CPP
namespace GameBoy {
#include "dmg.cpp"
#include "cgb.cpp"
#include "mmio/mmio.cpp"
#include "serialization.cpp"
LCD lcd;
@@ -18,22 +26,22 @@ void LCD::main() {
}
add_clocks(4);
status.lx += 4;
if(status.lx >= 456) scanline();
if(status.lx == 0) {
if(status.display_enable && status.lx == 0) {
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
}
if(status.lx == 252) {
if(status.display_enable && status.lx == 252) {
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
cpu.hblank();
}
}
}
void LCD::add_clocks(unsigned clocks) {
status.lx += clocks;
if(status.lx >= 456) scanline();
clock += clocks;
clock += clocks * cpu.frequency;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
co_switch(scheduler.active_thread = cpu.thread);
}
@@ -43,174 +51,84 @@ void LCD::scanline() {
status.lx -= 456;
if(++status.ly == 154) frame();
if(status.interrupt_lyc == true) {
if(status.display_enable && status.interrupt_lyc == true) {
if(status.ly == status.lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
}
if(status.ly < 144) render();
if(status.ly < 144) {
system.cgb() == false ? dmg_render() : cgb_render();
}
if(status.ly == 144) {
if(status.display_enable && status.ly == 144) {
cpu.interrupt_raise(CPU::Interrupt::Vblank);
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
}
}
void LCD::frame() {
system.interface->video_refresh(screen);
system.interface->input_poll();
interface->videoRefresh(screen);
cpu.mmio_joyp_poll();
status.ly = 0;
status.wyc = 0;
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
void LCD::render() {
for(unsigned n = 0; n < 160; n++) line[n] = 0x00;
if(status.display_enable == true) {
if(status.bg_enable == true) render_bg();
if(status.window_display_enable == true) render_window();
if(status.obj_enable == true) render_obj();
}
uint8_t *output = screen + status.ly * 160;
for(unsigned n = 0; n < 160; n++) output[n] = (3 - line[n]) * 0x55;
system.interface->lcd_scanline();
}
uint16 LCD::read_tile(bool select, unsigned x, unsigned y) {
unsigned tmaddr = 0x1800 + (select << 10), tdaddr;
tmaddr += (((y >> 3) << 5) + (x >> 3)) & 0x03ff;
if(status.bg_tiledata_select == 0) {
tdaddr = 0x1000 + ((int8)vram[tmaddr] << 4);
} else {
tdaddr = 0x0000 + (vram[tmaddr] << 4);
}
tdaddr += (y & 7) << 1;
return (vram[tdaddr + 0] << 0) | (vram[tdaddr + 1] << 8);
}
void LCD::render_bg() {
unsigned iy = (status.ly + status.scy) & 255;
unsigned ix = status.scx, tx = ix & 7;
unsigned data = read_tile(status.bg_tilemap_select, ix, iy);
for(unsigned ox = 0; ox < 160; ox++) {
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
line[ox] = status.bgp[palette];
ix = (ix + 1) & 255;
tx = (tx + 1) & 7;
if(tx == 0) data = read_tile(status.bg_tilemap_select, ix, iy);
}
}
void LCD::render_window() {
if(status.ly - status.wy >= 144U) return;
unsigned iy = status.ly - status.wy;
unsigned ix = (status.wx - 7) & 255, tx = ix & 7;
unsigned data = read_tile(status.window_tilemap_select, ix, iy);
for(unsigned ox = 0; ox < 160; ox++) {
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
if(ox - (status.wx - 7) < 160U) line[ox] = status.bgp[palette];
ix = (ix + 1) & 255;
tx = (tx + 1) & 7;
if(tx == 0) data = read_tile(status.window_tilemap_select, ix, iy);
}
}
void LCD::render_obj() {
unsigned obj_size = (status.obj_size == 0 ? 8 : 16);
unsigned sprite[10], sprites = 0;
//find first ten sprites on this scanline
for(unsigned s = 0; s < 40; s++) {
unsigned sy = oam[(s << 2) + 0] - 16;
unsigned sx = oam[(s << 2) + 1] - 8;
sy = status.ly - sy;
if(sy >= obj_size) continue;
sprite[sprites++] = s;
if(sprites == 10) break;
}
//sort by X-coordinate, when equal, lower address comes first
for(unsigned x = 0; x < sprites; x++) {
for(unsigned y = x + 1; y < sprites; y++) {
signed sx = oam[(sprite[x] << 2) + 1] - 8;
signed sy = oam[(sprite[y] << 2) + 1] - 8;
if(sy < sx) {
sprite[x] ^= sprite[y];
sprite[y] ^= sprite[x];
sprite[x] ^= sprite[y];
}
}
}
//render backwards, so that first sprite has highest priority
for(signed s = sprites - 1; s >= 0; s--) {
unsigned n = sprite[s] << 2;
unsigned sy = oam[n + 0] - 16;
unsigned sx = oam[n + 1] - 8;
unsigned tile = oam[n + 2];
unsigned attribute = oam[n + 3];
sy = status.ly - sy;
if(sy >= obj_size) continue;
if(attribute & 0x40) sy ^= (obj_size - 1);
unsigned tdaddr = (tile << 4) + (sy << 1);
uint8 d0 = vram[tdaddr + 0];
uint8 d1 = vram[tdaddr + 1];
unsigned xflip = attribute & 0x20 ? 7 : 0;
for(unsigned tx = 0; tx < 8; tx++) {
uint8 palette = ((d0 & (0x80 >> tx)) ? 1 : 0)
| ((d1 & (0x80 >> tx)) ? 2 : 0);
if(palette == 0) continue;
palette = status.obp[(bool)(attribute & 0x10)][palette];
unsigned ox = sx + (tx ^ xflip);
if(ox <= 159) {
if(attribute & 0x80) {
if(line[ox] > 0) continue;
}
line[ox] = palette;
}
}
}
unsigned LCD::hflip(unsigned data) const {
return ((data & 0x8080) >> 7) | ((data & 0x4040) >> 5)
| ((data & 0x2020) >> 3) | ((data & 0x1010) >> 1)
| ((data & 0x0808) << 1) | ((data & 0x0404) << 3)
| ((data & 0x0202) << 5) | ((data & 0x0101) << 7);
}
void LCD::power() {
create(Main, 4194304);
create(Main, 4 * 1024 * 1024);
for(unsigned n = 0x8000; n <= 0x9fff; n++) bus.mmio[n] = this; //VRAM
for(unsigned n = 0xff40; n <= 0xff4b; n++) bus.mmio[n] = this; //MMIO
for(unsigned n = 0xfe00; n <= 0xfe9f; n++) bus.mmio[n] = this; //OAM
for(unsigned n = 0; n < 8192; n++) vram[n] = 0x00;
for(unsigned n = 0; n < 160; n++) oam [n] = 0x00;
bus.mmio[0xff40] = this; //LCDC
bus.mmio[0xff41] = this; //STAT
bus.mmio[0xff42] = this; //SCY
bus.mmio[0xff43] = this; //SCX
bus.mmio[0xff44] = this; //LY
bus.mmio[0xff45] = this; //LYC
bus.mmio[0xff47] = this; //BGP
bus.mmio[0xff48] = this; //OBP0
bus.mmio[0xff49] = this; //OBP1
bus.mmio[0xff4a] = this; //WY
bus.mmio[0xff4b] = this; //WX
for(unsigned n = 0; n < 160 * 144; n++) screen[n] = 0x00;
if(system.cgb()) {
bus.mmio[0xff4f] = this; //VBK
bus.mmio[0xff68] = this; //BGPI
bus.mmio[0xff69] = this; //BGPD
bus.mmio[0xff6a] = this; //OBPI
bus.mmio[0xff6b] = this; //OBPD
}
for(auto &n : screen) n = 0x0000;
for(auto &n : line) n = 0x0000;
for(auto &n : origin) n = Origin::None;
for(auto &n : vram) n = 0x00;
for(auto &n : oam) n = 0x00;
for(auto &n : bgp) n = 0x00;
for(auto &n : obp[0]) n = 0x00;
for(auto &n : obp[1]) n = 0x00;
for(auto &n : bgpd) n = 0x0000;
for(auto &n : obpd) n = 0x0000;
status.lx = 0;
status.wyc = 0;
status.display_enable = 0;
status.window_tilemap_select = 0;
status.window_display_enable = 0;
status.bg_tiledata_select = 0;
status.bg_tilemap_select = 0;
status.obj_size = 0;
status.obj_enable = 0;
status.ob_size = 0;
status.ob_enable = 0;
status.bg_enable = 0;
status.interrupt_lyc = 0;
@@ -222,15 +140,16 @@ void LCD::power() {
status.scx = 0;
status.ly = 0;
status.lyc = 0;
for(unsigned n = 0; n < 4; n++) {
status.bgp[n] = n;
status.obp[0][n] = n;
status.obp[1][n] = n;
}
status.wy = 0;
status.wx = 0;
status.vram_bank = 0;
status.bgpi_increment = 0;
status.bgpi = 0;
status.obpi_increment = 0;
status.obpi = 0;
}
LCD::LCD() {

View File

@@ -3,6 +3,7 @@ struct LCD : Processor, MMIO {
struct Status {
unsigned lx;
unsigned wyc;
//$ff40 LCDC
bool display_enable;
@@ -10,8 +11,8 @@ struct LCD : Processor, MMIO {
bool window_display_enable;
bool bg_tiledata_select;
bool bg_tilemap_select;
bool obj_size;
bool obj_enable;
bool ob_size;
bool ob_enable;
bool bg_enable;
//$ff41 STAT
@@ -32,35 +33,57 @@ struct LCD : Processor, MMIO {
//$ff45 LYC
uint8 lyc;
//$ff47 BGP
uint8 bgp[4];
//$ff48 OBP0
//$ff49 OBP1
uint8 obp[2][4];
//$ff4a WY
uint8 wy;
//$ff4b WX
uint8 wx;
//$ff4f VBK
bool vram_bank;
//$ff68 BGPI
bool bgpi_increment;
uint6 bgpi;
//$ff6a OBPI
bool obpi_increment;
uint8 obpi;
} status;
uint8 screen[160 * 144];
uint8 vram[8192];
uint16 screen[160 * 144];
uint16 line[160];
struct Origin { enum : unsigned { None, BG, BGP, OB }; };
uint8 origin[160];
uint8 vram[16384]; //GB = 8192, GBC = 16384
uint8 oam[160];
uint8 line[160];
uint8 bgp[4];
uint8 obp[2][4];
uint8 bgpd[64];
uint8 obpd[64];
static void Main();
void main();
void add_clocks(unsigned clocks);
void scanline();
void frame();
void render();
uint16 read_tile(bool select, unsigned x, unsigned y);
void render_bg();
void render_window();
void render_obj();
unsigned hflip(unsigned data) const;
//dmg.cpp
void dmg_render();
uint16 dmg_read_tile(bool select, unsigned x, unsigned y);
void dmg_render_bg();
void dmg_render_window();
void dmg_render_ob();
//cgb.cpp
void cgb_render();
void cgb_read_tile(bool select, unsigned x, unsigned y, unsigned &tile, unsigned &attr, unsigned &data);
void cgb_render_bg();
void cgb_render_window();
void cgb_render_ob();
void power();

View File

@@ -1,7 +1,11 @@
#ifdef LCD_CPP
unsigned LCD::vram_addr(uint16 addr) const {
return (status.vram_bank * 0x2000) + (addr & 0x1fff);
}
uint8 LCD::mmio_read(uint16 addr) {
if(addr >= 0x8000 && addr <= 0x9fff) return vram[addr & 0x1fff];
if(addr >= 0x8000 && addr <= 0x9fff) return vram[vram_addr(addr)];
if(addr >= 0xfe00 && addr <= 0xfe9f) return oam[addr & 0xff];
if(addr == 0xff40) { //LCDC
@@ -10,17 +14,17 @@ uint8 LCD::mmio_read(uint16 addr) {
| (status.window_display_enable << 5)
| (status.bg_tiledata_select << 4)
| (status.bg_tilemap_select << 3)
| (status.obj_size << 2)
| (status.obj_enable << 1)
| (status.ob_size << 2)
| (status.ob_enable << 1)
| (status.bg_enable << 0);
}
if(addr == 0xff41) { //STAT
unsigned mode;
if(status.ly >= 144) mode = 1; //Vblank
else if(status.lx < 80) mode = 2; //OAM
if(status.ly >= 144) mode = 1; //Vblank
else if(status.lx < 80) mode = 2; //OAM
else if(status.lx < 252) mode = 3; //LCD
else mode = 0; //Hblank
else mode = 0; //Hblank
return (status.interrupt_lyc << 6)
| (status.interrupt_oam << 5)
@@ -47,24 +51,24 @@ uint8 LCD::mmio_read(uint16 addr) {
}
if(addr == 0xff47) { //BGP
return (status.bgp[3] << 6)
| (status.bgp[2] << 4)
| (status.bgp[1] << 2)
| (status.bgp[0] << 0);
return (bgp[3] << 6)
| (bgp[2] << 4)
| (bgp[1] << 2)
| (bgp[0] << 0);
}
if(addr == 0xff48) { //OBP0
return (status.obp[0][3] << 6)
| (status.obp[0][2] << 4)
| (status.obp[0][1] << 2)
| (status.obp[0][0] << 0);
return (obp[0][3] << 6)
| (obp[0][2] << 4)
| (obp[0][1] << 2)
| (obp[0][0] << 0);
}
if(addr == 0xff49) { //OBP1
return (status.obp[1][3] << 6)
| (status.obp[1][2] << 4)
| (status.obp[1][1] << 2)
| (status.obp[1][0] << 0);
return (obp[1][3] << 6)
| (obp[1][2] << 4)
| (obp[1][1] << 2)
| (obp[1][0] << 0);
}
if(addr == 0xff4a) { //WY
@@ -75,21 +79,33 @@ uint8 LCD::mmio_read(uint16 addr) {
return status.wx;
}
if(addr == 0xff69) { //BGPD
return bgpd[status.bgpi];
}
if(addr == 0xff6b) { //OBPD
return obpd[status.obpi];
}
return 0x00;
}
void LCD::mmio_write(uint16 addr, uint8 data) {
if(addr >= 0x8000 && addr <= 0x9fff) { vram[addr & 0x1fff] = data; return; }
if(addr >= 0x8000 && addr <= 0x9fff) { vram[vram_addr(addr)] = data; return; }
if(addr >= 0xfe00 && addr <= 0xfe9f) { oam[addr & 0xff] = data; return; }
if(addr == 0xff40) { //LCDC
if(status.display_enable == false && (data & 0x80)) {
status.lx = 0; //unverified behavior; fixes Super Mario Land 2 - Tree Zone
}
status.display_enable = data & 0x80;
status.window_tilemap_select = data & 0x40;
status.window_display_enable = data & 0x20;
status.bg_tiledata_select = data & 0x10;
status.bg_tilemap_select = data & 0x08;
status.obj_size = data & 0x04;
status.obj_enable = data & 0x02;
status.ob_size = data & 0x04;
status.ob_enable = data & 0x02;
status.bg_enable = data & 0x01;
return;
}
@@ -122,32 +138,27 @@ void LCD::mmio_write(uint16 addr, uint8 data) {
return;
}
if(addr == 0xff46) { //DMA
for(unsigned n = 0x00; n <= 0x9f; n++) bus.write(0xfe00 + n, bus.read((data << 8) + n));
return;
}
if(addr == 0xff47) { //BGP
status.bgp[3] = (data >> 6) & 3;
status.bgp[2] = (data >> 4) & 3;
status.bgp[1] = (data >> 2) & 3;
status.bgp[0] = (data >> 0) & 3;
bgp[3] = (data >> 6) & 3;
bgp[2] = (data >> 4) & 3;
bgp[1] = (data >> 2) & 3;
bgp[0] = (data >> 0) & 3;
return;
}
if(addr == 0xff48) { //OBP0
status.obp[0][3] = (data >> 6) & 3;
status.obp[0][2] = (data >> 4) & 3;
status.obp[0][1] = (data >> 2) & 3;
status.obp[0][0] = (data >> 0) & 3;
obp[0][3] = (data >> 6) & 3;
obp[0][2] = (data >> 4) & 3;
obp[0][1] = (data >> 2) & 3;
obp[0][0] = (data >> 0) & 3;
return;
}
if(addr == 0xff49) { //OBP1
status.obp[1][3] = (data >> 6) & 3;
status.obp[1][2] = (data >> 4) & 3;
status.obp[1][1] = (data >> 2) & 3;
status.obp[1][0] = (data >> 0) & 3;
obp[1][3] = (data >> 6) & 3;
obp[1][2] = (data >> 4) & 3;
obp[1][1] = (data >> 2) & 3;
obp[1][0] = (data >> 0) & 3;
return;
}
@@ -160,6 +171,33 @@ void LCD::mmio_write(uint16 addr, uint8 data) {
status.wx = data;
return;
}
if(addr == 0xff4f) { //VBK
status.vram_bank = data & 1;
return;
}
if(addr == 0xff68) { //BGPI
status.bgpi_increment = data & 0x80;
status.bgpi = data & 0x3f;
return;
}
if(addr == 0xff69) { //BGPD
bgpd[status.bgpi] = data;
if(status.bgpi_increment) status.bgpi++;
return;
}
if(addr == 0xff6a) { //OBPI
status.obpi_increment = data & 0x80;
status.obpi = data & 0x3f;
}
if(addr == 0xff6b) { //OBPD
obpd[status.obpi] = data;
if(status.obpi_increment) status.obpi++;
}
}
#endif

View File

@@ -1,2 +1,3 @@
unsigned vram_addr(uint16 addr) const;
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);

View File

@@ -1,15 +1,30 @@
#ifdef LCD_CPP
void LCD::serialize(serializer &s) {
Processor::serialize(s);
s.array(screen);
s.array(line);
s.array(origin);
s.array(vram);
s.array(oam);
s.array(bgp);
s.array(obp[0]);
s.array(obp[1]);
s.array(bgpd);
s.array(obpd);
s.integer(status.lx);
s.integer(status.wyc);
s.integer(status.display_enable);
s.integer(status.window_tilemap_select);
s.integer(status.window_display_enable);
s.integer(status.bg_tiledata_select);
s.integer(status.bg_tilemap_select);
s.integer(status.obj_size);
s.integer(status.obj_enable);
s.integer(status.ob_size);
s.integer(status.ob_enable);
s.integer(status.bg_enable);
s.integer(status.interrupt_lyc);
@@ -19,20 +34,20 @@ void LCD::serialize(serializer &s) {
s.integer(status.scy);
s.integer(status.scx);
s.integer(status.ly);
s.integer(status.lyc);
s.array(status.bgp);
s.array(status.obp[0]);
s.array(status.obp[1]);
s.integer(status.wy);
s.integer(status.wx);
s.array(screen);
s.array(vram);
s.array(oam);
s.array(line);
s.integer(status.vram_bank);
s.integer(status.bgpi_increment);
s.integer(status.bgpi);
s.integer(status.obpi_increment);
s.integer(status.obpi);
}
#endif

View File

@@ -42,7 +42,20 @@ Memory::~Memory() {
//
uint8 Bus::read(uint16 addr) {
return mmio[addr]->mmio_read(addr);
uint8 data = mmio[addr]->mmio_read(addr);
if(cheat.override[addr]) {
for(unsigned n = 0; n < cheat.size(); n++) {
if(cheat[n].addr == addr) {
if(cheat[n].comp > 255 || cheat[n].comp == data) {
data = cheat[n].data;
break;
}
}
}
}
return data;
}
void Bus::write(uint16 addr, uint8 data) {

View File

@@ -0,0 +1,135 @@
#ifdef SYSTEM_CPP
//SHA256 = 4bf5021be357ce523a59ac5f4efff5d6371ae50112a6db0adf4a75916ad760a9
const uint8_t System::BootROM::cgb[2048] = {
0x31,0xfe,0xff,0x3e,0x02,0xc3,0x7c,0x00,0xd3,0x00,0x98,0xa0,0x12,0xd3,0x00,0x80,
0x00,0x40,0x1e,0x53,0xd0,0x00,0x1f,0x42,0x1c,0x00,0x14,0x2a,0x4d,0x19,0x8c,0x7e,
0x00,0x7c,0x31,0x6e,0x4a,0x45,0x52,0x4a,0x00,0x00,0xff,0x53,0x1f,0x7c,0xff,0x03,
0x1f,0x00,0xff,0x1f,0xa7,0x00,0xef,0x1b,0x1f,0x00,0xef,0x1b,0x00,0x7c,0x00,0x00,
0xff,0x03,0xce,0xed,0x66,0x66,0xcc,0x0d,0x00,0x0b,0x03,0x73,0x00,0x83,0x00,0x0c,
0x00,0x0d,0x00,0x08,0x11,0x1f,0x88,0x89,0x00,0x0e,0xdc,0xcc,0x6e,0xe6,0xdd,0xdd,
0xd9,0x99,0xbb,0xbb,0x67,0x63,0x6e,0x0e,0xec,0xcc,0xdd,0xdc,0x99,0x9f,0xbb,0xb9,
0x33,0x3e,0x3c,0x42,0xb9,0xa5,0xb9,0xa5,0x42,0x3c,0x58,0x43,0xe0,0x70,0x3e,0xfc,
0xe0,0x47,0xcd,0x75,0x02,0xcd,0x00,0x02,0x26,0xd0,0xcd,0x03,0x02,0x21,0x00,0xfe,
0x0e,0xa0,0xaf,0x22,0x0d,0x20,0xfc,0x11,0x04,0x01,0x21,0x10,0x80,0x4c,0x1a,0xe2,
0x0c,0xcd,0xc6,0x03,0xcd,0xc7,0x03,0x13,0x7b,0xfe,0x34,0x20,0xf1,0x11,0x72,0x00,
0x06,0x08,0x1a,0x13,0x22,0x23,0x05,0x20,0xf9,0xcd,0xf0,0x03,0x3e,0x01,0xe0,0x4f,
0x3e,0x91,0xe0,0x40,0x21,0xb2,0x98,0x06,0x4e,0x0e,0x44,0xcd,0x91,0x02,0xaf,0xe0,
0x4f,0x0e,0x80,0x21,0x42,0x00,0x06,0x18,0xf2,0x0c,0xbe,0x20,0xfe,0x23,0x05,0x20,
0xf7,0x21,0x34,0x01,0x06,0x19,0x78,0x86,0x2c,0x05,0x20,0xfb,0x86,0x20,0xfe,0xcd,
0x1c,0x03,0x18,0x02,0x00,0x00,0xcd,0xd0,0x05,0xaf,0xe0,0x70,0x3e,0x11,0xe0,0x50,
0x21,0x00,0x80,0xaf,0x22,0xcb,0x6c,0x28,0xfb,0xc9,0x2a,0x12,0x13,0x0d,0x20,0xfa,
0xc9,0xe5,0x21,0x0f,0xff,0xcb,0x86,0xcb,0x46,0x28,0xfc,0xe1,0xc9,0x11,0x00,0xff,
0x21,0x03,0xd0,0x0e,0x0f,0x3e,0x30,0x12,0x3e,0x20,0x12,0x1a,0x2f,0xa1,0xcb,0x37,
0x47,0x3e,0x10,0x12,0x1a,0x2f,0xa1,0xb0,0x4f,0x7e,0xa9,0xe6,0xf0,0x47,0x2a,0xa9,
0xa1,0xb0,0x32,0x47,0x79,0x77,0x3e,0x30,0x12,0xc9,0x3e,0x80,0xe0,0x68,0xe0,0x6a,
0x0e,0x6b,0x2a,0xe2,0x05,0x20,0xfb,0x4a,0x09,0x43,0x0e,0x69,0x2a,0xe2,0x05,0x20,
0xfb,0xc9,0xc5,0xd5,0xe5,0x21,0x00,0xd8,0x06,0x01,0x16,0x3f,0x1e,0x40,0xcd,0x4a,
0x02,0xe1,0xd1,0xc1,0xc9,0x3e,0x80,0xe0,0x26,0xe0,0x11,0x3e,0xf3,0xe0,0x12,0xe0,
0x25,0x3e,0x77,0xe0,0x24,0x21,0x30,0xff,0xaf,0x0e,0x10,0x22,0x2f,0x0d,0x20,0xfb,
0xc9,0xcd,0x11,0x02,0xcd,0x62,0x02,0x79,0xfe,0x38,0x20,0x14,0xe5,0xaf,0xe0,0x4f,
0x21,0xa7,0x99,0x3e,0x38,0x22,0x3c,0xfe,0x3f,0x20,0xfa,0x3e,0x01,0xe0,0x4f,0xe1,
0xc5,0xe5,0x21,0x43,0x01,0xcb,0x7e,0xcc,0x89,0x05,0xe1,0xc1,0xcd,0x11,0x02,0x79,
0xd6,0x30,0xd2,0x06,0x03,0x79,0xfe,0x01,0xca,0x06,0x03,0x7d,0xfe,0xd1,0x28,0x21,
0xc5,0x06,0x03,0x0e,0x01,0x16,0x03,0x7e,0xe6,0xf8,0xb1,0x22,0x15,0x20,0xf8,0x0c,
0x79,0xfe,0x06,0x20,0xf0,0x11,0x11,0x00,0x19,0x05,0x20,0xe7,0x11,0xa1,0xff,0x19,
0xc1,0x04,0x78,0x1e,0x83,0xfe,0x62,0x28,0x06,0x1e,0xc1,0xfe,0x64,0x20,0x07,0x7b,
0xe0,0x13,0x3e,0x87,0xe0,0x14,0xfa,0x02,0xd0,0xfe,0x00,0x28,0x0a,0x3d,0xea,0x02,
0xd0,0x79,0xfe,0x01,0xca,0x91,0x02,0x0d,0xc2,0x91,0x02,0xc9,0x0e,0x26,0xcd,0x4a,
0x03,0xcd,0x11,0x02,0xcd,0x62,0x02,0x0d,0x20,0xf4,0xcd,0x11,0x02,0x3e,0x01,0xe0,
0x4f,0xcd,0x3e,0x03,0xcd,0x41,0x03,0xaf,0xe0,0x4f,0xcd,0x3e,0x03,0xc9,0x21,0x08,
0x00,0x11,0x51,0xff,0x0e,0x05,0xcd,0x0a,0x02,0xc9,0xc5,0xd5,0xe5,0x21,0x40,0xd8,
0x0e,0x20,0x7e,0xe6,0x1f,0xfe,0x1f,0x28,0x01,0x3c,0x57,0x2a,0x07,0x07,0x07,0xe6,
0x07,0x47,0x3a,0x07,0x07,0x07,0xe6,0x18,0xb0,0xfe,0x1f,0x28,0x01,0x3c,0x0f,0x0f,
0x0f,0x47,0xe6,0xe0,0xb2,0x22,0x78,0xe6,0x03,0x5f,0x7e,0x0f,0x0f,0xe6,0x1f,0xfe,
0x1f,0x28,0x01,0x3c,0x07,0x07,0xb3,0x22,0x0d,0x20,0xc7,0xe1,0xd1,0xc1,0xc9,0x0e,
0x00,0x1a,0xe6,0xf0,0xcb,0x49,0x28,0x02,0xcb,0x37,0x47,0x23,0x7e,0xb0,0x22,0x1a,
0xe6,0x0f,0xcb,0x49,0x20,0x02,0xcb,0x37,0x47,0x23,0x7e,0xb0,0x22,0x13,0xcb,0x41,
0x28,0x0d,0xd5,0x11,0xf8,0xff,0xcb,0x49,0x28,0x03,0x11,0x08,0x00,0x19,0xd1,0x0c,
0x79,0xfe,0x18,0x20,0xcc,0xc9,0x47,0xd5,0x16,0x04,0x58,0xcb,0x10,0x17,0xcb,0x13,
0x17,0x15,0x20,0xf6,0xd1,0x22,0x23,0x22,0x23,0xc9,0x3e,0x19,0xea,0x10,0x99,0x21,
0x2f,0x99,0x0e,0x0c,0x3d,0x28,0x08,0x32,0x0d,0x20,0xf9,0x2e,0x0f,0x18,0xf3,0xc9,
0x3e,0x01,0xe0,0x4f,0xcd,0x00,0x02,0x11,0x07,0x06,0x21,0x80,0x80,0x0e,0xc0,0x1a,
0x22,0x23,0x22,0x23,0x13,0x0d,0x20,0xf7,0x11,0x04,0x01,0xcd,0x8f,0x03,0x01,0xa8,
0xff,0x09,0xcd,0x8f,0x03,0x01,0xf8,0xff,0x09,0x11,0x72,0x00,0x0e,0x08,0x23,0x1a,
0x22,0x13,0x0d,0x20,0xf9,0x21,0xc2,0x98,0x06,0x08,0x3e,0x08,0x0e,0x10,0x22,0x0d,
0x20,0xfc,0x11,0x10,0x00,0x19,0x05,0x20,0xf3,0xaf,0xe0,0x4f,0x21,0xc2,0x98,0x3e,
0x08,0x22,0x3c,0xfe,0x18,0x20,0x02,0x2e,0xe2,0xfe,0x28,0x20,0x03,0x21,0x02,0x99,
0xfe,0x38,0x20,0xed,0x21,0xd8,0x08,0x11,0x40,0xd8,0x06,0x08,0x3e,0xff,0x12,0x13,
0x12,0x13,0x0e,0x02,0xcd,0x0a,0x02,0x3e,0x00,0x12,0x13,0x12,0x13,0x13,0x13,0x05,
0x20,0xea,0xcd,0x62,0x02,0x21,0x4b,0x01,0x7e,0xfe,0x33,0x20,0x0b,0x2e,0x44,0x1e,
0x30,0x2a,0xbb,0x20,0x49,0x1c,0x18,0x04,0x2e,0x4b,0x1e,0x01,0x2a,0xbb,0x20,0x3e,
0x2e,0x34,0x01,0x10,0x00,0x2a,0x80,0x47,0x0d,0x20,0xfa,0xea,0x00,0xd0,0x21,0xc7,
0x06,0x0e,0x00,0x2a,0xb8,0x28,0x08,0x0c,0x79,0xfe,0x4f,0x20,0xf6,0x18,0x1f,0x79,
0xd6,0x41,0x38,0x1c,0x21,0x16,0x07,0x16,0x00,0x5f,0x19,0xfa,0x37,0x01,0x57,0x7e,
0xba,0x28,0x0d,0x11,0x0e,0x00,0x19,0x79,0x83,0x4f,0xd6,0x5e,0x38,0xed,0x0e,0x00,
0x21,0x33,0x07,0x06,0x00,0x09,0x7e,0xe6,0x1f,0xea,0x08,0xd0,0x7e,0xe6,0xe0,0x07,
0x07,0x07,0xea,0x0b,0xd0,0xcd,0xe9,0x04,0xc9,0x11,0x91,0x07,0x21,0x00,0xd9,0xfa,
0x0b,0xd0,0x47,0x0e,0x1e,0xcb,0x40,0x20,0x02,0x13,0x13,0x1a,0x22,0x20,0x02,0x1b,
0x1b,0xcb,0x48,0x20,0x02,0x13,0x13,0x1a,0x22,0x13,0x13,0x20,0x02,0x1b,0x1b,0xcb,
0x50,0x28,0x05,0x1b,0x2b,0x1a,0x22,0x13,0x1a,0x22,0x13,0x0d,0x20,0xd7,0x21,0x00,
0xd9,0x11,0x00,0xda,0xcd,0x64,0x05,0xc9,0x21,0x12,0x00,0xfa,0x05,0xd0,0x07,0x07,
0x06,0x00,0x4f,0x09,0x11,0x40,0xd8,0x06,0x08,0xe5,0x0e,0x02,0xcd,0x0a,0x02,0x13,
0x13,0x13,0x13,0x13,0x13,0xe1,0x05,0x20,0xf0,0x11,0x42,0xd8,0x0e,0x02,0xcd,0x0a,
0x02,0x11,0x4a,0xd8,0x0e,0x02,0xcd,0x0a,0x02,0x2b,0x2b,0x11,0x44,0xd8,0x0e,0x02,
0xcd,0x0a,0x02,0xc9,0x0e,0x60,0x2a,0xe5,0xc5,0x21,0xe8,0x07,0x06,0x00,0x4f,0x09,
0x0e,0x08,0xcd,0x0a,0x02,0xc1,0xe1,0x0d,0x20,0xec,0xc9,0xfa,0x08,0xd0,0x11,0x18,
0x00,0x3c,0x3d,0x28,0x03,0x19,0x20,0xfa,0xc9,0xcd,0x1d,0x02,0x78,0xe6,0xff,0x28,
0x0f,0x21,0xe4,0x08,0x06,0x00,0x2a,0xb9,0x28,0x08,0x04,0x78,0xfe,0x0c,0x20,0xf6,
0x18,0x2d,0x78,0xea,0x05,0xd0,0x3e,0x1e,0xea,0x02,0xd0,0x11,0x0b,0x00,0x19,0x56,
0x7a,0xe6,0x1f,0x5f,0x21,0x08,0xd0,0x3a,0x22,0x7b,0x77,0x7a,0xe6,0xe0,0x07,0x07,
0x07,0x5f,0x21,0x0b,0xd0,0x3a,0x22,0x7b,0x77,0xcd,0xe9,0x04,0xcd,0x28,0x05,0xc9,
0xcd,0x11,0x02,0xfa,0x43,0x01,0xcb,0x7f,0x28,0x04,0xe0,0x4c,0x18,0x28,0x3e,0x04,
0xe0,0x4c,0x3e,0x01,0xe0,0x6c,0x21,0x00,0xda,0xcd,0x7b,0x05,0x06,0x10,0x16,0x00,
0x1e,0x08,0xcd,0x4a,0x02,0x21,0x7a,0x00,0xfa,0x00,0xd0,0x47,0x0e,0x02,0x2a,0xb8,
0xcc,0xda,0x03,0x0d,0x20,0xf8,0xc9,0x01,0x0f,0x3f,0x7e,0xff,0xff,0xc0,0x00,0xc0,
0xf0,0xf1,0x03,0x7c,0xfc,0xfe,0xfe,0x03,0x07,0x07,0x0f,0xe0,0xe0,0xf0,0xf0,0x1e,
0x3e,0x7e,0xfe,0x0f,0x0f,0x1f,0x1f,0xff,0xff,0x00,0x00,0x01,0x01,0x01,0x03,0xff,
0xff,0xe1,0xe0,0xc0,0xf0,0xf9,0xfb,0x1f,0x7f,0xf8,0xe0,0xf3,0xfd,0x3e,0x1e,0xe0,
0xf0,0xf9,0x7f,0x3e,0x7c,0xf8,0xe0,0xf8,0xf0,0xf0,0xf8,0x00,0x00,0x7f,0x7f,0x07,
0x0f,0x9f,0xbf,0x9e,0x1f,0xff,0xff,0x0f,0x1e,0x3e,0x3c,0xf1,0xfb,0x7f,0x7f,0xfe,
0xde,0xdf,0x9f,0x1f,0x3f,0x3e,0x3c,0xf8,0xf8,0x00,0x00,0x03,0x03,0x07,0x07,0xff,
0xff,0xc1,0xc0,0xf3,0xe7,0xf7,0xf3,0xc0,0xc0,0xc0,0xc0,0x1f,0x1f,0x1e,0x3e,0x3f,
0x1f,0x3e,0x3e,0x80,0x00,0x00,0x00,0x7c,0x1f,0x07,0x00,0x0f,0xff,0xfe,0x00,0x7c,
0xf8,0xf0,0x00,0x1f,0x0f,0x0f,0x00,0x7c,0xf8,0xf8,0x00,0x3f,0x3e,0x1c,0x00,0x0f,
0x0f,0x0f,0x00,0x7c,0xff,0xff,0x00,0x00,0xf8,0xf8,0x00,0x07,0x0f,0x0f,0x00,0x81,
0xff,0xff,0x00,0xf3,0xe1,0x80,0x00,0xe0,0xff,0x7f,0x00,0xfc,0xf0,0xc0,0x00,0x3e,
0x7c,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x16,0x36,0xd1,0xdb,0xf2,0x3c,0x8c,
0x92,0x3d,0x5c,0x58,0xc9,0x3e,0x70,0x1d,0x59,0x69,0x19,0x35,0xa8,0x14,0xaa,0x75,
0x95,0x99,0x34,0x6f,0x15,0xff,0x97,0x4b,0x90,0x17,0x10,0x39,0xf7,0xf6,0xa2,0x49,
0x4e,0x43,0x68,0xe0,0x8b,0xf0,0xce,0x0c,0x29,0xe8,0xb7,0x86,0x9a,0x52,0x01,0x9d,
0x71,0x9c,0xbd,0x5d,0x6d,0x67,0x3f,0x6b,0xb3,0x46,0x28,0xa5,0xc6,0xd3,0x27,0x61,
0x18,0x66,0x6a,0xbf,0x0d,0xf4,0x42,0x45,0x46,0x41,0x41,0x52,0x42,0x45,0x4b,0x45,
0x4b,0x20,0x52,0x2d,0x55,0x52,0x41,0x52,0x20,0x49,0x4e,0x41,0x49,0x4c,0x49,0x43,
0x45,0x20,0x52,0x7c,0x08,0x12,0xa3,0xa2,0x07,0x87,0x4b,0x20,0x12,0x65,0xa8,0x16,
0xa9,0x86,0xb1,0x68,0xa0,0x87,0x66,0x12,0xa1,0x30,0x3c,0x12,0x85,0x12,0x64,0x1b,
0x07,0x06,0x6f,0x6e,0x6e,0xae,0xaf,0x6f,0xb2,0xaf,0xb2,0xa8,0xab,0x6f,0xaf,0x86,
0xae,0xa2,0xa2,0x12,0xaf,0x13,0x12,0xa1,0x6e,0xaf,0xaf,0xad,0x06,0x4c,0x6e,0xaf,
0xaf,0x12,0x7c,0xac,0xa8,0x6a,0x6e,0x13,0xa0,0x2d,0xa8,0x2b,0xac,0x64,0xac,0x6d,
0x87,0xbc,0x60,0xb4,0x13,0x72,0x7c,0xb5,0xae,0xae,0x7c,0x7c,0x65,0xa2,0x6c,0x64,
0x85,0x80,0xb0,0x40,0x88,0x20,0x68,0xde,0x00,0x70,0xde,0x20,0x78,0x20,0x20,0x38,
0x20,0xb0,0x90,0x20,0xb0,0xa0,0xe0,0xb0,0xc0,0x98,0xb6,0x48,0x80,0xe0,0x50,0x1e,
0x1e,0x58,0x20,0xb8,0xe0,0x88,0xb0,0x10,0x20,0x00,0x10,0x20,0xe0,0x18,0xe0,0x18,
0x00,0x18,0xe0,0x20,0xa8,0xe0,0x20,0x18,0xe0,0x00,0x20,0x18,0xd8,0xc8,0x18,0xe0,
0x00,0xe0,0x40,0x28,0x28,0x28,0x18,0xe0,0x60,0x20,0x18,0xe0,0x00,0x00,0x08,0xe0,
0x18,0x30,0xd0,0xd0,0xd0,0x20,0xe0,0xe8,0xff,0x7f,0xbf,0x32,0xd0,0x00,0x00,0x00,
0x9f,0x63,0x79,0x42,0xb0,0x15,0xcb,0x04,0xff,0x7f,0x31,0x6e,0x4a,0x45,0x00,0x00,
0xff,0x7f,0xef,0x1b,0x00,0x02,0x00,0x00,0xff,0x7f,0x1f,0x42,0xf2,0x1c,0x00,0x00,
0xff,0x7f,0x94,0x52,0x4a,0x29,0x00,0x00,0xff,0x7f,0xff,0x03,0x2f,0x01,0x00,0x00,
0xff,0x7f,0xef,0x03,0xd6,0x01,0x00,0x00,0xff,0x7f,0xb5,0x42,0xc8,0x3d,0x00,0x00,
0x74,0x7e,0xff,0x03,0x80,0x01,0x00,0x00,0xff,0x67,0xac,0x77,0x13,0x1a,0x6b,0x2d,
0xd6,0x7e,0xff,0x4b,0x75,0x21,0x00,0x00,0xff,0x53,0x5f,0x4a,0x52,0x7e,0x00,0x00,
0xff,0x4f,0xd2,0x7e,0x4c,0x3a,0xe0,0x1c,0xed,0x03,0xff,0x7f,0x5f,0x25,0x00,0x00,
0x6a,0x03,0x1f,0x02,0xff,0x03,0xff,0x7f,0xff,0x7f,0xdf,0x01,0x12,0x01,0x00,0x00,
0x1f,0x23,0x5f,0x03,0xf2,0x00,0x09,0x00,0xff,0x7f,0xea,0x03,0x1f,0x01,0x00,0x00,
0x9f,0x29,0x1a,0x00,0x0c,0x00,0x00,0x00,0xff,0x7f,0x7f,0x02,0x1f,0x00,0x00,0x00,
0xff,0x7f,0xe0,0x03,0x06,0x02,0x20,0x01,0xff,0x7f,0xeb,0x7e,0x1f,0x00,0x00,0x7c,
0xff,0x7f,0xff,0x3f,0x00,0x7e,0x1f,0x00,0xff,0x7f,0xff,0x03,0x1f,0x00,0x00,0x00,
0xff,0x03,0x1f,0x00,0x0c,0x00,0x00,0x00,0xff,0x7f,0x3f,0x03,0x93,0x01,0x00,0x00,
0x00,0x00,0x00,0x42,0x7f,0x03,0xff,0x7f,0xff,0x7f,0x8c,0x7e,0x00,0x7c,0x00,0x00,
0xff,0x7f,0xef,0x1b,0x80,0x61,0x00,0x00,0xff,0x7f,0x00,0x7c,0xe0,0x03,0x1f,0x7c,
0x1f,0x00,0xff,0x03,0x40,0x41,0x42,0x20,0x21,0x22,0x80,0x81,0x82,0x10,0x11,0x12,
0x12,0xb0,0x79,0xb8,0xad,0x16,0x17,0x07,0xba,0x05,0x7c,0x13,0x00,0x00,0x00,0x00,
};
#endif

View File

@@ -1,6 +1,6 @@
#ifdef SYSTEM_CPP
//MD5SUM = 32fbbd84168d3482956eb3c5051637f5
//SHA256 = cf053eccb4ccafff9e67339d4e78e98dce7d1ed59be819d2a1ba2232c6fce1c7
const uint8_t System::BootROM::dmg[256] = {
0x31,0xfe,0xff,0xaf,0x21,0xff,0x9f,0x32,0xcb,0x7c,0x20,0xfb,0x21,0x26,0xff,0x0e,
0x11,0x3e,0x80,0x32,0xe2,0x0c,0x3e,0xf3,0xe2,0x32,0x3e,0x77,0x77,0x3e,0xfc,0xe0,

View File

@@ -1,6 +1,6 @@
#ifdef SYSTEM_CPP
//MD5SUM = d574d4f9c12f305074798f54c091a8b4
//SHA256 = 0e4ddff32fc9d1eeaae812a157dd246459b00c9e14f2f61751f661f32361e360
const uint8_t System::BootROM::sgb[256] = {
0x31,0xfe,0xff,0x3e,0x30,0xe0,0x00,0xaf,0x21,0xff,0x9f,0x32,0xcb,0x7c,0x20,0xfb,
0x21,0x26,0xff,0x0e,0x11,0x3e,0x80,0x32,0xe2,0x0c,0x3e,0xf3,0xe2,0x32,0x3e,0x77,

View File

@@ -29,6 +29,7 @@ bool System::unserialize(serializer &s) {
if(version != Info::SerializerVersion) return false;
//if(crc32 != 0) return false;
power();
serialize_all(s);
return true;
}

View File

@@ -5,6 +5,7 @@ namespace GameBoy {
#include "bootrom-dmg.cpp"
#include "bootrom-sgb.cpp"
#include "bootrom-cgb.cpp"
#include "serialization.cpp"
System system;
@@ -33,11 +34,12 @@ void System::runthreadtosave() {
}
}
void System::init(Interface *interface_) {
interface = interface_;
void System::init() {
assert(interface != 0);
}
void System::load() {
void System::load(Revision revision) {
this->revision = revision;
serialize_init();
}

View File

@@ -4,21 +4,31 @@ enum class Input : unsigned {
Up, Down, Left, Right, B, A, Select, Start,
};
struct System {
struct System : property<System> {
enum class Revision : unsigned {
GameBoy,
SuperGameBoy,
GameBoyColor,
};
readonly<Revision> revision;
inline bool dmg() const { return revision == Revision::GameBoy; }
inline bool sgb() const { return revision == Revision::SuperGameBoy; }
inline bool cgb() const { return revision == Revision::GameBoyColor; }
struct BootROM {
static const uint8 dmg[256];
static const uint8 sgb[256];
static const uint8 dmg[ 256];
static const uint8 sgb[ 256];
static const uint8 cgb[2048];
} bootROM;
void run();
void runtosave();
void runthreadtosave();
void init(Interface*);
void load();
void init();
void load(Revision);
void power();
Interface *interface;
unsigned clocks_executed;
//serialization.cpp

82
bsnes/gameboy/video/video.cpp Executable file
View File

@@ -0,0 +1,82 @@
#include <gameboy/gameboy.hpp>
#define VIDEO_CPP
namespace GameBoy {
Video video;
unsigned Video::palette_dmg(unsigned color) const {
unsigned R = monochrome[color][0] * 1023.0;
unsigned G = monochrome[color][1] * 1023.0;
unsigned B = monochrome[color][2] * 1023.0;
return (R << 20) + (G << 10) + (B << 0);
}
unsigned Video::palette_sgb(unsigned color) const {
unsigned R = (3 - color) * 341;
unsigned G = (3 - color) * 341;
unsigned B = (3 - color) * 341;
return (R << 20) + (G << 10) + (B << 0);
}
unsigned Video::palette_cgb(unsigned color) const {
unsigned r = (color >> 0) & 31;
unsigned g = (color >> 5) & 31;
unsigned b = (color >> 10) & 31;
unsigned R = (r * 26 + g * 4 + b * 2);
unsigned G = ( g * 24 + b * 8);
unsigned B = (r * 6 + g * 4 + b * 22);
R = min(960, R);
G = min(960, G);
B = min(960, B);
return (R << 20) + (G << 10) + (B << 0);
}
void Video::generate(Format format) {
if(system.dmg()) for(unsigned n = 0; n < 4; n++) palette[n] = palette_dmg(n);
if(system.sgb()) for(unsigned n = 0; n < 4; n++) palette[n] = palette_sgb(n);
if(system.cgb()) for(unsigned n = 0; n < (1 << 15); n++) palette[n] = palette_cgb(n);
if(format == Format::RGB24) {
for(unsigned n = 0; n < (1 << 15); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 6) & 0xff0000) + ((color >> 4) & 0x00ff00) + ((color >> 2) & 0x0000ff);
}
}
if(format == Format::RGB16) {
for(unsigned n = 0; n < (1 << 15); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 14) & 0xf800) + ((color >> 9) & 0x07e0) + ((color >> 5) & 0x001f);
}
}
if(format == Format::RGB15) {
for(unsigned n = 0; n < (1 << 15); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 15) & 0x7c00) + ((color >> 10) & 0x03e0) + ((color >> 5) & 0x001f);
}
}
}
Video::Video() {
palette = new unsigned[1 << 15];
}
Video::~Video() {
delete[] palette;
}
const double Video::monochrome[4][3] = {
{ 0.605, 0.734, 0.059 },
{ 0.543, 0.672, 0.059 },
{ 0.188, 0.383, 0.188 },
{ 0.059, 0.219, 0.059 },
};
}

17
bsnes/gameboy/video/video.hpp Executable file
View File

@@ -0,0 +1,17 @@
struct Video {
enum class Format : unsigned { RGB30, RGB24, RGB16, RGB15 };
unsigned *palette;
unsigned palette_dmg(unsigned color) const;
unsigned palette_sgb(unsigned color) const;
unsigned palette_cgb(unsigned color) const;
void generate(Format format);
Video();
~Video();
private:
static const double monochrome[4][3];
};
extern Video video;

View File

@@ -19,6 +19,9 @@ ifeq ($(platform),)
ifeq ($(uname),)
platform := win
delete = del $(subst /,\,$1)
else ifneq ($(findstring CYGWIN,$(uname)),)
platform := win
delete = del $(subst /,\,$1)
else ifneq ($(findstring Darwin,$(uname)),)
platform := osx
delete = rm -f $1
@@ -32,12 +35,15 @@ ifeq ($(compiler),)
ifeq ($(platform),win)
compiler := gcc
else ifeq ($(platform),osx)
compiler := gcc-mp-4.5
compiler := gcc-mp-4.6
else
compiler := gcc-4.5
compiler := gcc-4.6
endif
endif
c := $(compiler) -std=gnu99
cpp := $(subst cc,++,$(compiler)) -std=gnu++0x
ifeq ($(prefix),)
prefix := /usr/local
endif

View File

@@ -2,17 +2,15 @@
#define NALL_ANY_HPP
#include <typeinfo>
#include <type_traits>
#include <nall/static.hpp>
#include <nall/type_traits.hpp>
namespace nall {
class any {
public:
struct any {
bool empty() const { return container; }
const std::type_info& type() const { return container ? container->type() : typeid(void); }
template<typename T> any& operator=(const T& value_) {
typedef typename static_if<
typedef typename type_if<
std::is_array<T>::value,
typename std::remove_extent<typename std::add_const<T>::type>::type*,
T

View File

@@ -2,150 +2,288 @@
#define NALL_ARRAY_HPP
#include <stdlib.h>
#include <algorithm>
#include <initializer_list>
#include <type_traits>
#include <utility>
#include <nall/algorithm.hpp>
#include <nall/bit.hpp>
#include <nall/concept.hpp>
#include <nall/foreach.hpp>
#include <nall/sort.hpp>
#include <nall/type_traits.hpp>
#include <nall/utility.hpp>
namespace nall {
//dynamic vector array
//neither constructor nor destructor is ever invoked;
//thus, this should only be used for POD objects.
template<typename T> class array {
protected:
T *pool;
unsigned poolsize, buffersize;
public:
unsigned size() const { return buffersize; }
unsigned capacity() const { return poolsize; }
template<typename T, typename Enable = void> struct array;
void reset() {
if(pool) free(pool);
pool = 0;
poolsize = 0;
buffersize = 0;
//non-reference array
//===================
template<typename T> struct array<T, typename std::enable_if<!std::is_reference<T>::value>::type> {
struct exception_out_of_bounds{};
protected:
T *pool;
unsigned poolsize, objectsize;
public:
unsigned size() const { return objectsize; }
unsigned capacity() const { return poolsize; }
void reset() {
if(pool) free(pool);
pool = nullptr;
poolsize = 0;
objectsize = 0;
}
void reserve(unsigned newsize) {
if(newsize == poolsize) return;
pool = (T*)realloc(pool, newsize * sizeof(T));
poolsize = newsize;
objectsize = min(objectsize, newsize);
}
void resize(unsigned newsize) {
if(newsize > poolsize) reserve(bit::round(newsize)); //round reserve size up to power of 2
objectsize = newsize;
}
T* get(unsigned minsize = 0) {
if(minsize > objectsize) resize(minsize);
return pool;
}
void append(const T data) {
operator()(objectsize) = data;
}
void append(const T data[], unsigned length) {
for(unsigned n = 0; n < length; n++) operator()(objectsize) = data[n];
}
void remove() {
if(size > 0) resize(size - 1); //remove last element only
}
void remove(unsigned index, unsigned count = 1) {
for(unsigned i = index; count + i < objectsize; i++) {
pool[i] = pool[count + i];
}
if(count + index >= objectsize) resize(index); //every element >= index was removed
else resize(objectsize - count);
}
void reserve(unsigned newsize) {
if(newsize == poolsize) return;
void sort() {
nall::sort(pool, objectsize);
}
pool = (T*)realloc(pool, newsize * sizeof(T));
poolsize = newsize;
buffersize = min(buffersize, newsize);
template<typename Comparator> void sort(const Comparator &lessthan) {
nall::sort(pool, objectsize, lessthan);
}
optional<unsigned> find(const T data) {
for(unsigned n = 0; n < size(); n++) if(pool[n] == data) return { true, n };
return { false, 0u };
}
void clear() {
memset(pool, 0, objectsize * sizeof(T));
}
array() : pool(nullptr), poolsize(0), objectsize(0) {
}
array(std::initializer_list<T> list) : pool(nullptr), poolsize(0), objectsize(0) {
for(auto &data : list) append(data);
}
~array() {
reset();
}
//copy
array& operator=(const array &source) {
if(pool) free(pool);
objectsize = source.objectsize;
poolsize = source.poolsize;
pool = (T*)malloc(sizeof(T) * poolsize); //allocate entire pool size,
memcpy(pool, source.pool, sizeof(T) * objectsize); //... but only copy used pool objects
return *this;
}
array(const array &source) : pool(nullptr), poolsize(0), objectsize(0) {
operator=(source);
}
//move
array& operator=(array &&source) {
if(pool) free(pool);
pool = source.pool;
poolsize = source.poolsize;
objectsize = source.objectsize;
source.pool = nullptr;
source.reset();
return *this;
}
array(array &&source) : pool(nullptr), poolsize(0), objectsize(0) {
operator=(std::move(source));
}
//access
inline T& operator[](unsigned position) {
if(position >= objectsize) throw exception_out_of_bounds();
return pool[position];
}
inline const T& operator[](unsigned position) const {
if(position >= objectsize) throw exception_out_of_bounds();
return pool[position];
}
inline T& operator()(unsigned position) {
if(position >= objectsize) resize(position + 1);
return pool[position];
}
inline const T& operator()(unsigned position, const T& data) {
if(position >= objectsize) return data;
return pool[position];
}
//iteration
T* begin() { return &pool[0]; }
T* end() { return &pool[objectsize]; }
const T* begin() const { return &pool[0]; }
const T* end() const { return &pool[objectsize]; }
};
//reference array
//===============
template<typename TR> struct array<TR, typename std::enable_if<std::is_reference<TR>::value>::type> {
struct exception_out_of_bounds{};
protected:
typedef typename std::remove_reference<TR>::type T;
T **pool;
unsigned poolsize, objectsize;
public:
unsigned size() const { return objectsize; }
unsigned capacity() const { return poolsize; }
void reset() {
if(pool) free(pool);
pool = nullptr;
poolsize = 0;
objectsize = 0;
}
void reserve(unsigned newsize) {
if(newsize == poolsize) return;
pool = (T**)realloc(pool, sizeof(T*) * newsize);
poolsize = newsize;
objectsize = min(objectsize, newsize);
}
void resize(unsigned newsize) {
if(newsize > poolsize) reserve(bit::round(newsize));
objectsize = newsize;
}
template<typename... Args>
bool append(T& data, Args&&... args) {
bool result = append(data);
append(std::forward<Args>(args)...);
return result;
}
bool append(T& data) {
if(find(data)) return false;
unsigned offset = objectsize++;
if(offset >= poolsize) resize(offset + 1);
pool[offset] = &data;
return true;
}
bool remove(T& data) {
if(auto position = find(data)) {
for(signed i = position(); i < objectsize - 1; i++) pool[i] = pool[i + 1];
resize(objectsize - 1);
return true;
}
return false;
}
void resize(unsigned newsize) {
if(newsize > poolsize) reserve(bit::round(newsize)); //round reserve size up to power of 2
buffersize = newsize;
}
optional<unsigned> find(const T& data) {
for(unsigned n = 0; n < objectsize; n++) if(pool[n] == &data) return { true, n };
return { false, 0u };
}
T* get(unsigned minsize = 0) {
if(minsize > buffersize) resize(minsize);
if(minsize > buffersize) throw "array[] out of bounds";
return pool;
}
template<typename... Args> array(Args&&... args) : pool(nullptr), poolsize(0), objectsize(0) {
construct(std::forward<Args>(args)...);
}
void append(const T data) {
operator[](buffersize) = data;
}
~array() {
reset();
}
void remove() {
if(size > 0) resize(size - 1); //remove last element only
}
array& operator=(const array &source) {
if(pool) free(pool);
objectsize = source.objectsize;
poolsize = source.poolsize;
pool = (T**)malloc(sizeof(T*) * poolsize);
memcpy(pool, source.pool, sizeof(T*) * objectsize);
return *this;
}
template<typename U> void insert(unsigned index, const U list) {
unsigned listsize = container_size(list);
resize(buffersize + listsize);
memmove(pool + index + listsize, pool + index, (buffersize - index) * sizeof(T));
foreach(item, list) pool[index++] = item;
}
array& operator=(const array &&source) {
if(pool) free(pool);
pool = source.pool;
poolsize = source.poolsize;
objectsize = source.objectsize;
source.pool = nullptr;
source.reset();
return *this;
}
void insert(unsigned index, const T item) {
insert(index, array<T>{ item });
}
T& operator[](unsigned position) const {
if(position >= objectsize) throw exception_out_of_bounds();
return *pool[position];
}
void remove(unsigned index, unsigned count = 1) {
for(unsigned i = index; count + i < buffersize; i++) {
pool[i] = pool[count + i];
}
if(count + index >= buffersize) resize(index); //every element >= index was removed
else resize(buffersize - count);
}
optional<unsigned> find(const T data) {
for(unsigned i = 0; i < size(); i++) if(pool[i] == data) return { true, i };
return { false, 0 };
}
void clear() {
memset(pool, 0, buffersize * sizeof(T));
}
array() : pool(0), poolsize(0), buffersize(0) {
}
array(std::initializer_list<T> list) : pool(0), poolsize(0), buffersize(0) {
for(const T *p = list.begin(); p != list.end(); ++p) append(*p);
}
~array() {
reset();
}
//copy
array& operator=(const array &source) {
if(pool) free(pool);
buffersize = source.buffersize;
poolsize = source.poolsize;
pool = (T*)malloc(sizeof(T) * poolsize); //allocate entire pool size,
memcpy(pool, source.pool, sizeof(T) * buffersize); //... but only copy used pool objects
return *this;
}
array(const array &source) : pool(0), poolsize(0), buffersize(0) {
operator=(source);
}
//move
array& operator=(array &&source) {
if(pool) free(pool);
pool = source.pool;
poolsize = source.poolsize;
buffersize = source.buffersize;
source.pool = 0;
source.reset();
return *this;
}
array(array &&source) : pool(0), poolsize(0), buffersize(0) {
operator=(std::move(source));
}
//index
inline T& operator[](unsigned index) {
if(index >= buffersize) resize(index + 1);
if(index >= buffersize) throw "array[] out of bounds";
return pool[index];
}
inline const T& operator[](unsigned index) const {
if(index >= buffersize) throw "array[] out of bounds";
return pool[index];
}
//iteration
T* begin() { return &pool[0]; }
T* end() { return &pool[buffersize]; }
const T* begin() const { return &pool[0]; }
const T* end() const { return &pool[buffersize]; }
//iteration
struct iterator {
bool operator!=(const iterator &source) const { return position != source.position; }
T& operator*() { return source.operator[](position); }
iterator& operator++() { position++; return *this; }
iterator(const array &source, unsigned position) : source(source), position(position) {}
private:
const array &source;
unsigned position;
};
template<typename T> struct has_size<array<T>> { enum { value = true }; };
iterator begin() { return iterator(*this, 0); }
iterator end() { return iterator(*this, objectsize); }
const iterator begin() const { return iterator(*this, 0); }
const iterator end() const { return iterator(*this, objectsize); }
private:
void construct() {
}
void construct(const array& source) { operator=(source); }
void construct(const array&& source) { operator=(std::move(source)); }
template<typename... Args> void construct(T& data, Args&&... args) {
append(data);
construct(std::forward<Args>(args)...);
}
};
}
#endif

101
bsnes/nall/atoi.hpp Executable file
View File

@@ -0,0 +1,101 @@
#ifndef NALL_ATOI_HPP
#define NALL_ATOI_HPP
namespace nall {
//note: this header is intended to form the base for user-defined literals;
//once they are supported by GCC. eg:
//unsigned operator "" b(const char *s) { return binary(s); }
//-> signed data = 1001b;
//(0b1001 is nicer, but is not part of the C++ standard)
constexpr inline uintmax_t binary_(const char *s, uintmax_t sum = 0) {
return (
*s == '0' || *s == '1' ? binary_(s + 1, (sum << 1) | *s - '0') :
sum
);
}
constexpr inline uintmax_t octal_(const char *s, uintmax_t sum = 0) {
return (
*s >= '0' && *s <= '7' ? octal_(s + 1, (sum << 3) | *s - '0') :
sum
);
}
constexpr inline uintmax_t decimal_(const char *s, uintmax_t sum = 0) {
return (
*s >= '0' && *s <= '9' ? decimal_(s + 1, (sum * 10) + *s - '0') :
sum
);
}
constexpr inline uintmax_t hex_(const char *s, uintmax_t sum = 0) {
return (
*s >= 'A' && *s <= 'F' ? hex_(s + 1, (sum << 4) | *s - 'A' + 10) :
*s >= 'a' && *s <= 'f' ? hex_(s + 1, (sum << 4) | *s - 'a' + 10) :
*s >= '0' && *s <= '9' ? hex_(s + 1, (sum << 4) | *s - '0') :
sum
);
}
//
constexpr inline uintmax_t binary(const char *s) {
return (
*s == '0' && *(s + 1) == 'B' ? binary_(s + 2) :
*s == '0' && *(s + 1) == 'b' ? binary_(s + 2) :
*s == '%' ? binary_(s + 1) :
binary_(s)
);
}
constexpr inline uintmax_t octal(const char *s) {
return (
octal_(s)
);
}
constexpr inline intmax_t integer(const char *s) {
return (
*s == '+' ? +decimal_(s + 1) :
*s == '-' ? -decimal_(s + 1) :
decimal_(s)
);
}
constexpr inline uintmax_t decimal(const char *s) {
return (
decimal_(s)
);
}
constexpr inline uintmax_t hex(const char *s) {
return (
*s == '0' && *(s + 1) == 'X' ? hex_(s + 2) :
*s == '0' && *(s + 1) == 'x' ? hex_(s + 2) :
*s == '$' ? hex_(s + 1) :
hex_(s)
);
}
constexpr inline intmax_t numeral(const char *s) {
return (
*s == '0' && *(s + 1) == 'X' ? hex_(s + 2) :
*s == '0' && *(s + 1) == 'x' ? hex_(s + 2) :
*s == '0' && *(s + 1) == 'B' ? binary_(s + 2) :
*s == '0' && *(s + 1) == 'b' ? binary_(s + 2) :
*s == '0' ? octal_(s + 1) :
*s == '+' ? +decimal_(s + 1) :
*s == '-' ? -decimal_(s + 1) :
decimal_(s)
);
}
inline double fp(const char *s) {
return atof(s);
}
}
#endif

View File

@@ -5,8 +5,7 @@
#include <nall/stdint.hpp>
namespace nall {
class base64 {
public:
struct base64 {
static bool encode(char *&output, const uint8_t* input, unsigned inlength) {
output = new char[inlength * 8 / 6 + 6]();

View File

@@ -2,45 +2,56 @@
#define NALL_BIT_HPP
namespace nall {
template<int bits> inline unsigned uclamp(const unsigned x) {
enum { y = (1U << (bits - 1)) + ((1U << (bits - 1)) - 1) };
template<unsigned bits>
constexpr inline uintmax_t uclamp(const uintmax_t x) {
enum : uintmax_t { b = 1ull << (bits - 1), y = b * 2 - 1 };
return y + ((x - y) & -(x < y)); //min(x, y);
}
template<int bits> inline unsigned uclip(const unsigned x) {
enum { m = (1U << (bits - 1)) + ((1U << (bits - 1)) - 1) };
template<unsigned bits>
constexpr inline uintmax_t uclip(const uintmax_t x) {
enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 };
return (x & m);
}
template<int bits> inline signed sclamp(const signed x) {
enum { b = 1U << (bits - 1), m = (1U << (bits - 1)) - 1 };
template<unsigned bits>
constexpr inline intmax_t sclamp(const intmax_t x) {
enum : intmax_t { b = 1ull << (bits - 1), m = b - 1 };
return (x > m) ? m : (x < -b) ? -b : x;
}
template<int bits> inline signed sclip(const signed x) {
enum { b = 1U << (bits - 1), m = (1U << bits) - 1 };
template<unsigned bits>
constexpr inline intmax_t sclip(const intmax_t x) {
enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 };
return ((x & m) ^ b) - b;
}
namespace bit {
//lowest(0b1110) == 0b0010
template<typename T> inline T lowest(const T x) {
constexpr inline uintmax_t lowest(const uintmax_t x) {
return x & -x;
}
//clear_lowest(0b1110) == 0b1100
template<typename T> inline T clear_lowest(const T x) {
constexpr inline uintmax_t clear_lowest(const uintmax_t x) {
return x & (x - 1);
}
//set_lowest(0b0101) == 0b0111
template<typename T> inline T set_lowest(const T x) {
constexpr inline uintmax_t set_lowest(const uintmax_t x) {
return x | (x + 1);
}
//count number of bits set in a byte
inline unsigned count(uintmax_t x) {
unsigned count = 0;
do count += x & 1; while(x >>= 1);
return count;
}
//round up to next highest single bit:
//round(15) == 16, round(16) == 16, round(17) == 32
inline unsigned round(unsigned x) {
inline uintmax_t round(uintmax_t x) {
if((x & (x - 1)) == 0) return x;
while(x & (x - 1)) x &= x - 1;
return x << 1;

214
bsnes/nall/bps/delta.hpp Executable file
View File

@@ -0,0 +1,214 @@
#ifndef NALL_BPS_DELTA_HPP
#define NALL_BPS_DELTA_HPP
#include <nall/crc32.hpp>
#include <nall/file.hpp>
#include <nall/filemap.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
namespace nall {
struct bpsdelta {
inline void source(const uint8_t *data, unsigned size);
inline void target(const uint8_t *data, unsigned size);
inline bool source(const string &filename);
inline bool target(const string &filename);
inline bool create(const string &filename, const string &metadata = "");
protected:
enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy };
enum : unsigned { Granularity = 1 };
struct Node {
unsigned offset;
Node *next;
inline Node() : offset(0), next(nullptr) {}
inline ~Node() { if(next) delete next; }
};
filemap sourceFile;
const uint8_t *sourceData;
unsigned sourceSize;
filemap targetFile;
const uint8_t *targetData;
unsigned targetSize;
};
void bpsdelta::source(const uint8_t *data, unsigned size) {
sourceData = data;
sourceSize = size;
}
void bpsdelta::target(const uint8_t *data, unsigned size) {
targetData = data;
targetSize = size;
}
bool bpsdelta::source(const string &filename) {
if(sourceFile.open(filename, filemap::mode::read) == false) return false;
source(sourceFile.data(), sourceFile.size());
return true;
}
bool bpsdelta::target(const string &filename) {
if(targetFile.open(filename, filemap::mode::read) == false) return false;
target(targetFile.data(), targetFile.size());
return true;
}
bool bpsdelta::create(const string &filename, const string &metadata) {
file modifyFile;
if(modifyFile.open(filename, file::mode::write) == false) return false;
uint32_t sourceChecksum = ~0, modifyChecksum = ~0;
unsigned sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0;
auto write = [&](uint8_t data) {
modifyFile.write(data);
modifyChecksum = crc32_adjust(modifyChecksum, data);
};
auto encode = [&](uint64_t data) {
while(true) {
uint64_t x = data & 0x7f;
data >>= 7;
if(data == 0) {
write(0x80 | x);
break;
}
write(x);
data--;
}
};
write('B');
write('P');
write('S');
write('1');
encode(sourceSize);
encode(targetSize);
unsigned markupSize = metadata.length();
encode(markupSize);
for(unsigned n = 0; n < markupSize; n++) write(metadata[n]);
Node *sourceTree[65536], *targetTree[65536];
for(unsigned n = 0; n < 65536; n++) sourceTree[n] = 0, targetTree[n] = 0;
//source tree creation
for(unsigned offset = 0; offset < sourceSize; offset++) {
uint16_t symbol = sourceData[offset + 0];
sourceChecksum = crc32_adjust(sourceChecksum, symbol);
if(offset < sourceSize - 1) symbol |= sourceData[offset + 1] << 8;
Node *node = new Node;
node->offset = offset;
node->next = sourceTree[symbol];
sourceTree[symbol] = node;
}
unsigned targetReadLength = 0;
auto targetReadFlush = [&]() {
if(targetReadLength) {
encode(TargetRead | ((targetReadLength - 1) << 2));
unsigned offset = outputOffset - targetReadLength;
while(targetReadLength) write(targetData[offset++]), targetReadLength--;
}
};
while(outputOffset < targetSize) {
unsigned maxLength = 0, maxOffset = 0, mode = TargetRead;
uint16_t symbol = targetData[outputOffset + 0];
if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8;
{ //source read
unsigned length = 0, offset = outputOffset;
while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) {
length++;
offset++;
}
if(length > maxLength) maxLength = length, mode = SourceRead;
}
{ //source copy
Node *node = sourceTree[symbol];
while(node) {
unsigned length = 0, x = node->offset, y = outputOffset;
while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++;
if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = SourceCopy;
node = node->next;
}
}
{ //target copy
Node *node = targetTree[symbol];
while(node) {
unsigned length = 0, x = node->offset, y = outputOffset;
while(y < targetSize && targetData[x++] == targetData[y++]) length++;
if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = TargetCopy;
node = node->next;
}
//target tree append
node = new Node;
node->offset = outputOffset;
node->next = targetTree[symbol];
targetTree[symbol] = node;
}
{ //target read
if(maxLength < 4) {
maxLength = min((unsigned)Granularity, targetSize - outputOffset);
mode = TargetRead;
}
}
if(mode != TargetRead) targetReadFlush();
switch(mode) {
case SourceRead:
encode(SourceRead | ((maxLength - 1) << 2));
break;
case TargetRead:
//delay write to group sequential TargetRead commands into one
targetReadLength += maxLength;
break;
case SourceCopy:
case TargetCopy:
encode(mode | ((maxLength - 1) << 2));
signed relativeOffset;
if(mode == SourceCopy) {
relativeOffset = maxOffset - sourceRelativeOffset;
sourceRelativeOffset = maxOffset + maxLength;
} else {
relativeOffset = maxOffset - targetRelativeOffset;
targetRelativeOffset = maxOffset + maxLength;
}
encode((relativeOffset < 0) | (abs(relativeOffset) << 1));
break;
}
outputOffset += maxLength;
}
targetReadFlush();
sourceChecksum = ~sourceChecksum;
for(unsigned n = 0; n < 32; n += 8) write(sourceChecksum >> n);
uint32_t targetChecksum = crc32_calculate(targetData, targetSize);
for(unsigned n = 0; n < 32; n += 8) write(targetChecksum >> n);
uint32_t outputChecksum = ~modifyChecksum;
for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n);
modifyFile.close();
return true;
}
}
#endif

152
bsnes/nall/bps/linear.hpp Executable file
View File

@@ -0,0 +1,152 @@
#ifndef NALL_BPS_LINEAR_HPP
#define NALL_BPS_LINEAR_HPP
#include <nall/crc32.hpp>
#include <nall/file.hpp>
#include <nall/filemap.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
namespace nall {
struct bpslinear {
inline void source(const uint8_t *data, unsigned size);
inline void target(const uint8_t *data, unsigned size);
inline bool source(const string &filename);
inline bool target(const string &filename);
inline bool create(const string &filename, const string &metadata = "");
protected:
enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy };
enum : unsigned { Granularity = 1 };
filemap sourceFile;
const uint8_t *sourceData;
unsigned sourceSize;
filemap targetFile;
const uint8_t *targetData;
unsigned targetSize;
};
void bpslinear::source(const uint8_t *data, unsigned size) {
sourceData = data;
sourceSize = size;
}
void bpslinear::target(const uint8_t *data, unsigned size) {
targetData = data;
targetSize = size;
}
bool bpslinear::source(const string &filename) {
if(sourceFile.open(filename, filemap::mode::read) == false) return false;
source(sourceFile.data(), sourceFile.size());
return true;
}
bool bpslinear::target(const string &filename) {
if(targetFile.open(filename, filemap::mode::read) == false) return false;
target(targetFile.data(), targetFile.size());
return true;
}
bool bpslinear::create(const string &filename, const string &metadata) {
file modifyFile;
if(modifyFile.open(filename, file::mode::write) == false) return false;
uint32_t modifyChecksum = ~0;
unsigned targetRelativeOffset = 0, outputOffset = 0;
auto write = [&](uint8_t data) {
modifyFile.write(data);
modifyChecksum = crc32_adjust(modifyChecksum, data);
};
auto encode = [&](uint64_t data) {
while(true) {
uint64_t x = data & 0x7f;
data >>= 7;
if(data == 0) {
write(0x80 | x);
break;
}
write(x);
data--;
}
};
unsigned targetReadLength = 0;
auto targetReadFlush = [&]() {
if(targetReadLength) {
encode(TargetRead | ((targetReadLength - 1) << 2));
unsigned offset = outputOffset - targetReadLength;
while(targetReadLength) write(targetData[offset++]), targetReadLength--;
}
};
write('B');
write('P');
write('S');
write('1');
encode(sourceSize);
encode(targetSize);
unsigned markupSize = metadata.length();
encode(markupSize);
for(unsigned n = 0; n < markupSize; n++) write(metadata[n]);
while(outputOffset < targetSize) {
unsigned sourceLength = 0;
for(unsigned n = 0; outputOffset + n < min(sourceSize, targetSize); n++) {
if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break;
sourceLength++;
}
unsigned rleLength = 0;
for(unsigned n = 1; outputOffset + n < targetSize; n++) {
if(targetData[outputOffset] != targetData[outputOffset + n]) break;
rleLength++;
}
if(rleLength >= 4) {
//write byte to repeat
targetReadLength++;
outputOffset++;
targetReadFlush();
//copy starting from repetition byte
encode(TargetCopy | ((rleLength - 1) << 2));
unsigned relativeOffset = (outputOffset - 1) - targetRelativeOffset;
encode(relativeOffset << 1);
outputOffset += rleLength;
targetRelativeOffset = outputOffset - 1;
} else if(sourceLength >= 4) {
targetReadFlush();
encode(SourceRead | ((sourceLength - 1) << 2));
outputOffset += sourceLength;
} else {
targetReadLength += Granularity;
outputOffset += Granularity;
}
}
targetReadFlush();
uint32_t sourceChecksum = crc32_calculate(sourceData, sourceSize);
for(unsigned n = 0; n < 32; n += 8) write(sourceChecksum >> n);
uint32_t targetChecksum = crc32_calculate(targetData, targetSize);
for(unsigned n = 0; n < 32; n += 8) write(targetChecksum >> n);
uint32_t outputChecksum = ~modifyChecksum;
for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n);
modifyFile.close();
return true;
}
}
#endif

121
bsnes/nall/bps/metadata.hpp Executable file
View File

@@ -0,0 +1,121 @@
#ifndef NALL_BPS_METADATA_HPP
#define NALL_BPS_METADATA_HPP
#include <nall/crc32.hpp>
#include <nall/file.hpp>
#include <nall/filemap.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
namespace nall {
struct bpsmetadata {
inline bool load(const string &filename);
inline bool save(const string &filename, const string &metadata);
inline string metadata() const;
protected:
file sourceFile;
string metadataString;
};
bool bpsmetadata::load(const string &filename) {
if(sourceFile.open(filename, file::mode::read) == false) return false;
auto read = [&]() -> uint8_t {
return sourceFile.read();
};
auto decode = [&]() -> uint64_t {
uint64_t data = 0, shift = 1;
while(true) {
uint8_t x = read();
data += (x & 0x7f) * shift;
if(x & 0x80) break;
shift <<= 7;
data += shift;
}
return data;
};
if(read() != 'B') return false;
if(read() != 'P') return false;
if(read() != 'S') return false;
if(read() != '1') return false;
decode();
decode();
unsigned metadataSize = decode();
char data[metadataSize + 1];
for(unsigned n = 0; n < metadataSize; n++) data[n] = read();
data[metadataSize] = 0;
metadataString = (const char*)data;
return true;
}
bool bpsmetadata::save(const string &filename, const string &metadata) {
file targetFile;
if(targetFile.open(filename, file::mode::write) == false) return false;
if(sourceFile.open() == false) return false;
sourceFile.seek(0);
auto read = [&]() -> uint8_t {
return sourceFile.read();
};
auto decode = [&]() -> uint64_t {
uint64_t data = 0, shift = 1;
while(true) {
uint8_t x = read();
data += (x & 0x7f) * shift;
if(x & 0x80) break;
shift <<= 7;
data += shift;
}
return data;
};
uint32_t checksum = ~0;
auto write = [&](uint8_t data) {
targetFile.write(data);
checksum = crc32_adjust(checksum, data);
};
auto encode = [&](uint64_t data) {
while(true) {
uint64_t x = data & 0x7f;
data >>= 7;
if(data == 0) {
write(0x80 | x);
break;
}
write(x);
data--;
}
};
for(unsigned n = 0; n < 4; n++) write(read());
encode(decode());
encode(decode());
unsigned sourceLength = decode();
unsigned targetLength = metadata.length();
encode(targetLength);
sourceFile.seek(sourceLength, file::index::relative);
for(unsigned n = 0; n < targetLength; n++) write(metadata[n]);
unsigned length = sourceFile.size() - sourceFile.offset() - 4;
for(unsigned n = 0; n < length; n++) write(read());
uint32_t outputChecksum = ~checksum;
for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n);
targetFile.close();
return true;
}
string bpsmetadata::metadata() const {
return metadataString;
}
}
#endif

219
bsnes/nall/bps/patch.hpp Executable file
View File

@@ -0,0 +1,219 @@
#ifndef NALL_BPS_PATCH_HPP
#define NALL_BPS_PATCH_HPP
#include <nall/crc32.hpp>
#include <nall/file.hpp>
#include <nall/filemap.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
namespace nall {
struct bpspatch {
inline bool modify(const uint8_t *data, unsigned size);
inline void source(const uint8_t *data, unsigned size);
inline void target(uint8_t *data, unsigned size);
inline bool modify(const string &filename);
inline bool source(const string &filename);
inline bool target(const string &filename);
inline string metadata() const;
inline unsigned size() const;
enum result : unsigned {
unknown,
success,
patch_too_small,
patch_invalid_header,
source_too_small,
target_too_small,
source_checksum_invalid,
target_checksum_invalid,
patch_checksum_invalid,
};
inline result apply();
protected:
enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy };
filemap modifyFile;
const uint8_t *modifyData;
unsigned modifySize;
filemap sourceFile;
const uint8_t *sourceData;
unsigned sourceSize;
filemap targetFile;
uint8_t *targetData;
unsigned targetSize;
unsigned modifySourceSize;
unsigned modifyTargetSize;
unsigned modifyMarkupSize;
string metadataString;
};
bool bpspatch::modify(const uint8_t *data, unsigned size) {
if(size < 19) return false;
modifyData = data;
modifySize = size;
unsigned offset = 4;
auto decode = [&]() -> uint64_t {
uint64_t data = 0, shift = 1;
while(true) {
uint8_t x = modifyData[offset++];
data += (x & 0x7f) * shift;
if(x & 0x80) break;
shift <<= 7;
data += shift;
}
return data;
};
modifySourceSize = decode();
modifyTargetSize = decode();
modifyMarkupSize = decode();
char buffer[modifyMarkupSize + 1];
for(unsigned n = 0; n < modifyMarkupSize; n++) buffer[n] = modifyData[offset++];
buffer[modifyMarkupSize] = 0;
metadataString = (const char*)buffer;
return true;
}
void bpspatch::source(const uint8_t *data, unsigned size) {
sourceData = data;
sourceSize = size;
}
void bpspatch::target(uint8_t *data, unsigned size) {
targetData = data;
targetSize = size;
}
bool bpspatch::modify(const string &filename) {
if(modifyFile.open(filename, filemap::mode::read) == false) return false;
return modify(modifyFile.data(), modifyFile.size());
}
bool bpspatch::source(const string &filename) {
if(sourceFile.open(filename, filemap::mode::read) == false) return false;
source(sourceFile.data(), sourceFile.size());
return true;
}
bool bpspatch::target(const string &filename) {
file fp;
if(fp.open(filename, file::mode::write) == false) return false;
fp.truncate(modifyTargetSize);
fp.close();
if(targetFile.open(filename, filemap::mode::readwrite) == false) return false;
target(targetFile.data(), targetFile.size());
return true;
}
string bpspatch::metadata() const {
return metadataString;
}
unsigned bpspatch::size() const {
return modifyTargetSize;
}
bpspatch::result bpspatch::apply() {
if(modifySize < 19) return result::patch_too_small;
uint32_t modifyChecksum = ~0, targetChecksum = ~0;
unsigned modifyOffset = 0, sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0;
auto read = [&]() -> uint8_t {
uint8_t data = modifyData[modifyOffset++];
modifyChecksum = crc32_adjust(modifyChecksum, data);
return data;
};
auto decode = [&]() -> uint64_t {
uint64_t data = 0, shift = 1;
while(true) {
uint8_t x = read();
data += (x & 0x7f) * shift;
if(x & 0x80) break;
shift <<= 7;
data += shift;
}
return data;
};
auto write = [&](uint8_t data) {
targetData[outputOffset++] = data;
targetChecksum = crc32_adjust(targetChecksum, data);
};
if(read() != 'B') return result::patch_invalid_header;
if(read() != 'P') return result::patch_invalid_header;
if(read() != 'S') return result::patch_invalid_header;
if(read() != '1') return result::patch_invalid_header;
modifySourceSize = decode();
modifyTargetSize = decode();
modifyMarkupSize = decode();
for(unsigned n = 0; n < modifyMarkupSize; n++) read();
if(modifySourceSize > sourceSize) return result::source_too_small;
if(modifyTargetSize > targetSize) return result::target_too_small;
while(modifyOffset < modifySize - 12) {
unsigned length = decode();
unsigned mode = length & 3;
length = (length >> 2) + 1;
switch(mode) {
case SourceRead:
while(length--) write(sourceData[outputOffset]);
break;
case TargetRead:
while(length--) write(read());
break;
case SourceCopy:
case TargetCopy:
signed offset = decode();
bool negative = offset & 1;
offset >>= 1;
if(negative) offset = -offset;
if(mode == SourceCopy) {
sourceRelativeOffset += offset;
while(length--) write(sourceData[sourceRelativeOffset++]);
} else {
targetRelativeOffset += offset;
while(length--) write(targetData[targetRelativeOffset++]);
}
break;
}
}
uint32_t modifySourceChecksum = 0, modifyTargetChecksum = 0, modifyModifyChecksum = 0;
for(unsigned n = 0; n < 32; n += 8) modifySourceChecksum |= read() << n;
for(unsigned n = 0; n < 32; n += 8) modifyTargetChecksum |= read() << n;
uint32_t checksum = ~modifyChecksum;
for(unsigned n = 0; n < 32; n += 8) modifyModifyChecksum |= read() << n;
uint32_t sourceChecksum = crc32_calculate(sourceData, modifySourceSize);
targetChecksum = ~targetChecksum;
if(sourceChecksum != modifySourceChecksum) return result::source_checksum_invalid;
if(targetChecksum != modifyTargetChecksum) return result::target_checksum_invalid;
if(checksum != modifyModifyChecksum) return result::patch_checksum_invalid;
return result::success;
}
}
#endif

View File

@@ -1,18 +1,56 @@
#ifndef NALL_COMPOSITOR_HPP
#define NALL_COMPOSITOR_HPP
#include <nall/detect.hpp>
#include <nall/intrinsics.hpp>
namespace nall {
struct compositor {
inline static bool enabled();
inline static bool enable(bool status);
#if defined(PLATFORM_X)
enum class Compositor : unsigned { Unknown, Metacity, Xfwm4 };
inline static Compositor detect();
inline static bool enabled_metacity();
inline static bool enable_metacity(bool status);
inline static bool enabled_xfwm4();
inline static bool enable_xfwm4(bool status);
#endif
};
#if defined(PLATFORM_X)
bool compositor::enabled() {
//Metacity
bool compositor::enabled_metacity() {
FILE *fp = popen("gconftool-2 --get /apps/metacity/general/compositing_manager", "r");
if(fp == 0) return false;
char buffer[512];
if(fgets(buffer, sizeof buffer, fp) == 0) return false;
if(!memcmp(buffer, "true", 4)) return true;
return false;
}
bool compositor::enable_metacity(bool status) {
FILE *fp;
if(status) {
fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager true", "r");
} else {
fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager false", "r");
}
if(fp == 0) return false;
pclose(fp);
return true;
}
//Xfwm4
bool compositor::enabled_xfwm4() {
FILE *fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing'", "r");
if(fp == 0) return false;
@@ -23,7 +61,7 @@ bool compositor::enabled() {
return false;
}
bool compositor::enable(bool status) {
bool compositor::enable_xfwm4(bool status) {
FILE *fp;
if(status) {
fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing' -t 'bool' -s 'true'", "r");
@@ -35,7 +73,42 @@ bool compositor::enable(bool status) {
return true;
}
#elif defined(PLATFORM_WIN)
//General
compositor::Compositor compositor::detect() {
Compositor result = Compositor::Unknown;
FILE *fp;
char buffer[512];
fp = popen("pidof metacity", "r");
if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Metacity;
pclose(fp);
fp = popen("pidof xfwm4", "r");
if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Xfwm4;
pclose(fp);
return result;
}
bool compositor::enabled() {
switch(detect()) {
case Compositor::Metacity: return enabled_metacity();
case Compositor::Xfwm4: return enabled_xfwm4();
default: return false;
}
}
bool compositor::enable(bool status) {
switch(detect()) {
case Compositor::Metacity: return enable_metacity(status);
case Compositor::Xfwm4: return enable_xfwm4(status);
default: return false;
}
}
#elif defined(PLATFORM_WINDOWS)
bool compositor::enabled() {
HMODULE module = GetModuleHandleW(L"dwmapi");

View File

@@ -1,34 +0,0 @@
#ifndef NALL_CONCEPT_HPP
#define NALL_CONCEPT_HPP
#include <nall/static.hpp>
#include <nall/utility.hpp>
namespace nall {
//unsigned count() const;
template<typename T> struct has_count { enum { value = false }; };
//unsigned length() const;
template<typename T> struct has_length { enum { value = false }; };
//unsigned size() const;
template<typename T> struct has_size { enum { value = false }; };
template<typename T> unsigned container_size(const T& object, typename mp_enable_if<has_count<T>>::type = 0) {
return object.count();
}
template<typename T> unsigned container_size(const T& object, typename mp_enable_if<has_length<T>>::type = 0) {
return object.length();
}
template<typename T> unsigned container_size(const T& object, typename mp_enable_if<has_size<T>>::type = 0) {
return object.size();
}
template<typename T> unsigned container_size(const T& object, typename mp_enable_if<std::is_array<T>>::type = 0) {
return sizeof(T) / sizeof(typename std::remove_extent<T>::type);
}
}
#endif

View File

@@ -32,7 +32,7 @@ namespace nall {
string desc;
type_t type;
string get() const {
inline string get() const {
switch(type) {
case boolean_t: return { *(bool*)data };
case signed_t: return { *(signed*)data };
@@ -43,7 +43,7 @@ namespace nall {
return "???";
}
void set(string s) {
inline void set(string s) {
switch(type) {
case boolean_t: *(bool*)data = (s == "true"); break;
case signed_t: *(signed*)data = integer(s); break;
@@ -53,24 +53,27 @@ namespace nall {
}
}
};
linear_vector<item_t> list;
vector<item_t> list;
template<typename T>
void attach(T &data, const char *name, const char *desc = "") {
unsigned n = list.size();
list[n].data = (uintptr_t)&data;
list[n].name = name;
list[n].desc = desc;
if(configuration_traits::is_boolean<T>::value) list[n].type = boolean_t;
else if(configuration_traits::is_signed<T>::value) list[n].type = signed_t;
else if(configuration_traits::is_unsigned<T>::value) list[n].type = unsigned_t;
else if(configuration_traits::is_double<T>::value) list[n].type = double_t;
else if(configuration_traits::is_string<T>::value) list[n].type = string_t;
else list[n].type = unknown_t;
inline void append(T &data, const char *name, const char *desc = "") {
item_t item = { (uintptr_t)&data, name, desc };
if(configuration_traits::is_boolean<T>::value) item.type = boolean_t;
else if(configuration_traits::is_signed<T>::value) item.type = signed_t;
else if(configuration_traits::is_unsigned<T>::value) item.type = unsigned_t;
else if(configuration_traits::is_double<T>::value) item.type = double_t;
else if(configuration_traits::is_string<T>::value) item.type = string_t;
else item.type = unknown_t;
list.append(item);
}
virtual bool load(const char *filename) {
//deprecated
template<typename T>
inline void attach(T &data, const char *name, const char *desc = "") {
append(data, name, desc);
}
inline virtual bool load(const string &filename) {
string data;
if(data.readfile(filename) == true) {
data.replace("\r", "");
@@ -100,7 +103,7 @@ namespace nall {
}
}
virtual bool save(const char *filename) const {
inline virtual bool save(const string &filename) const {
file fp;
if(fp.open(filename, file::mode::write)) {
for(unsigned i = 0; i < list.size(); i++) {

View File

@@ -1,30 +0,0 @@
#ifndef NALL_DETECT_HPP
#define NALL_DETECT_HPP
/* Compiler detection */
#if defined(__GNUC__)
#define COMPILER_GCC
#elif defined(_MSC_VER)
#define COMPILER_VISUALC
#endif
/* Platform detection */
#if defined(_WIN32)
#define PLATFORM_WIN
#elif defined(__APPLE__)
#define PLATFORM_OSX
#elif defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#define PLATFORM_X
#endif
/* Endian detection */
#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64)
#define ARCH_LSB
#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__)
#define ARCH_MSB
#endif
#endif

View File

@@ -1,11 +1,12 @@
#ifndef NALL_DIRECTORY_HPP
#define NALL_DIRECTORY_HPP
#include <nall/foreach.hpp>
#include <nall/intrinsics.hpp>
#include <nall/sort.hpp>
#include <nall/string.hpp>
#include <nall/vector.hpp>
#if defined(_WIN32)
#if defined(PLATFORM_WINDOWS)
#include <nall/windows/utf8.hpp>
#else
#include <dirent.h>
@@ -22,7 +23,7 @@ struct directory {
static lstring contents(const string &pathname, const string &pattern = "*");
};
#if defined(_WIN32)
#if defined(PLATFORM_WINDOWS)
inline bool directory::exists(const string &pathname) {
DWORD result = GetFileAttributes(utf16_t(pathname));
if(result == INVALID_FILE_ATTRIBUTES) return false;
@@ -55,8 +56,8 @@ struct directory {
}
FindClose(handle);
}
if(list.size() > 0) sort(&list[0], list.size());
foreach(name, list) name.append("/"); //must append after sorting
if(list.size() > 0) list.sort();
for(auto &name : list) name.append("/"); //must append after sorting
return list;
}
@@ -82,14 +83,14 @@ struct directory {
}
FindClose(handle);
}
if(list.size() > 0) sort(&list[0], list.size());
if(list.size() > 0) list.sort();
return list;
}
inline lstring directory::contents(const string &pathname, const string &pattern) {
lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files
lstring files = directory::files(pathname, pattern);
foreach(file, files) folders.append(file);
for(auto &file : files) folders.append(file);
return folders;
}
#else
@@ -115,8 +116,8 @@ struct directory {
}
closedir(dp);
}
if(list.size() > 0) sort(&list[0], list.size());
foreach(name, list) name.append("/"); //must append after sorting
if(list.size() > 0) list.sort();
for(auto &name : list) name.append("/"); //must append after sorting
return list;
}
@@ -135,14 +136,14 @@ struct directory {
}
closedir(dp);
}
if(list.size() > 0) sort(&list[0], list.size());
if(list.size() > 0) list.sort();
return list;
}
inline lstring directory::contents(const string &pathname, const string &pattern) {
lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files
lstring files = directory::files(pathname, pattern);
foreach(file, files) folders.append(file);
for(auto &file : files) folders.append(file);
return folders;
}
#endif

View File

@@ -3,14 +3,14 @@
//dynamic linking support
#include <nall/detect.hpp>
#include <nall/intrinsics.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
#include <nall/utility.hpp>
#if defined(PLATFORM_X) || defined(PLATFORM_OSX)
#include <dlfcn.h>
#elif defined(PLATFORM_WIN)
#elif defined(PLATFORM_WINDOWS)
#include <windows.h>
#include <nall/windows/utf8.hpp>
#endif
@@ -81,7 +81,7 @@ namespace nall {
dlclose((void*)handle);
handle = 0;
}
#elif defined(PLATFORM_WIN)
#elif defined(PLATFORM_WINDOWS)
inline bool library::open(const char *name, const char *path) {
if(handle) close();
string filepath(path, *path && !strend(path, "/") && !strend(path, "\\") ? "\\" : "", name, ".dll");

13
bsnes/nall/dsp.hpp Executable file
View File

@@ -0,0 +1,13 @@
#ifndef NALL_DSP_HPP
#define NALL_DSP_HPP
#include <algorithm>
#ifdef __SSE__
#include <xmmintrin.h>
#endif
#define NALL_DSP_INTERNAL_HPP
#include <nall/dsp/core.hpp>
#undef NALL_DSP_INTERNAL_HPP
#endif

51
bsnes/nall/dsp/buffer.hpp Executable file
View File

@@ -0,0 +1,51 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct Buffer {
double **sample;
uint16_t rdoffset;
uint16_t wroffset;
unsigned channels;
void setChannels(unsigned channels) {
for(unsigned c = 0; c < this->channels; c++) {
if(sample[c]) delete[] sample[c];
}
if(sample) delete[] sample;
this->channels = channels;
if(channels == 0) return;
sample = new double*[channels];
for(unsigned c = 0; c < channels; c++) {
sample[c] = new double[65536]();
}
}
inline double& read(unsigned channel, signed offset = 0) {
return sample[channel][(uint16_t)(rdoffset + offset)];
}
inline double& write(unsigned channel, signed offset = 0) {
return sample[channel][(uint16_t)(wroffset + offset)];
}
inline void clear() {
for(unsigned c = 0; c < channels; c++) {
for(unsigned n = 0; n < 65536; n++) {
sample[c][n] = 0;
}
}
rdoffset = 0;
wroffset = 0;
}
Buffer() {
channels = 0;
}
~Buffer() {
setChannels(0);
}
};
#endif

167
bsnes/nall/dsp/core.hpp Executable file
View File

@@ -0,0 +1,167 @@
#ifdef NALL_DSP_INTERNAL_HPP
#include <math.h>
#include <nall/stdint.hpp>
namespace nall {
//precision: can be float, double or long double
#define real float
struct DSP;
struct Resampler {
DSP &dsp;
real frequency;
virtual void setFrequency() = 0;
virtual void clear() = 0;
virtual void sample() = 0;
Resampler(DSP &dsp) : dsp(dsp) {}
};
struct DSP {
enum class ResampleEngine : unsigned {
Nearest,
Linear,
Cosine,
Cubic,
Hermite,
Average,
Sinc,
};
inline void setChannels(unsigned channels);
inline void setPrecision(unsigned precision);
inline void setFrequency(real frequency); //inputFrequency
inline void setVolume(real volume);
inline void setBalance(real balance);
inline void setResampler(ResampleEngine resamplingEngine);
inline void setResamplerFrequency(real frequency); //outputFrequency
inline void sample(signed channel[]);
inline bool pending();
inline void read(signed channel[]);
inline void clear();
inline DSP();
inline ~DSP();
protected:
friend class ResampleNearest;
friend class ResampleLinear;
friend class ResampleCosine;
friend class ResampleCubic;
friend class ResampleAverage;
friend class ResampleHermite;
friend class ResampleSinc;
struct Settings {
unsigned channels;
unsigned precision;
real frequency;
real volume;
real balance;
//internal
real intensity;
real intensityInverse;
} settings;
Resampler *resampler;
inline void write(real channel[]);
#include "buffer.hpp"
Buffer buffer;
Buffer output;
inline void adjustVolume();
inline void adjustBalance();
inline signed clamp(const unsigned bits, const signed x);
};
#include "resample/nearest.hpp"
#include "resample/linear.hpp"
#include "resample/cosine.hpp"
#include "resample/cubic.hpp"
#include "resample/hermite.hpp"
#include "resample/average.hpp"
#include "resample/sinc.hpp"
#include "settings.hpp"
void DSP::sample(signed channel[]) {
for(unsigned c = 0; c < settings.channels; c++) {
buffer.write(c) = (real)channel[c] * settings.intensityInverse;
}
buffer.wroffset++;
resampler->sample();
}
bool DSP::pending() {
return output.rdoffset != output.wroffset;
}
void DSP::read(signed channel[]) {
adjustVolume();
adjustBalance();
for(unsigned c = 0; c < settings.channels; c++) {
channel[c] = clamp(settings.precision, output.read(c) * settings.intensity);
}
output.rdoffset++;
}
void DSP::write(real channel[]) {
for(unsigned c = 0; c < settings.channels; c++) {
output.write(c) = channel[c];
}
output.wroffset++;
}
void DSP::adjustVolume() {
for(unsigned c = 0; c < settings.channels; c++) {
output.read(c) *= settings.volume;
}
}
void DSP::adjustBalance() {
if(settings.channels != 2) return; //TODO: support > 2 channels
if(settings.balance < 0.0) output.read(1) *= 1.0 + settings.balance;
if(settings.balance > 0.0) output.read(0) *= 1.0 - settings.balance;
}
signed DSP::clamp(const unsigned bits, const signed x) {
const signed b = 1U << (bits - 1);
const signed m = (1U << (bits - 1)) - 1;
return (x > m) ? m : (x < -b) ? -b : x;
}
void DSP::clear() {
buffer.clear();
output.clear();
resampler->clear();
}
DSP::DSP() {
setResampler(ResampleEngine::Hermite);
setResamplerFrequency(44100.0);
setChannels(2);
setPrecision(16);
setFrequency(44100.0);
setVolume(1.0);
setBalance(0.0);
clear();
}
DSP::~DSP() {
if(resampler) delete resampler;
}
#undef real
}
#endif

View File

@@ -0,0 +1,72 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct ResampleAverage : Resampler {
inline void setFrequency();
inline void clear();
inline void sample();
inline void sampleLinear();
ResampleAverage(DSP &dsp) : Resampler(dsp) {}
real fraction;
real step;
};
void ResampleAverage::setFrequency() {
fraction = 0.0;
step = dsp.settings.frequency / frequency;
}
void ResampleAverage::clear() {
fraction = 0.0;
}
void ResampleAverage::sample() {
//can only average if input frequency >= output frequency
if(step < 1.0) return sampleLinear();
fraction += 1.0;
real scalar = 1.0;
if(fraction > step) scalar = 1.0 - (fraction - step);
for(unsigned c = 0; c < dsp.settings.channels; c++) {
dsp.output.write(c) += dsp.buffer.read(c) * scalar;
}
if(fraction >= step) {
for(unsigned c = 0; c < dsp.settings.channels; c++) {
dsp.output.write(c) /= step;
}
dsp.output.wroffset++;
fraction -= step;
for(unsigned c = 0; c < dsp.settings.channels; c++) {
dsp.output.write(c) = dsp.buffer.read(c) * fraction;
}
}
dsp.buffer.rdoffset++;
}
void ResampleAverage::sampleLinear() {
while(fraction <= 1.0) {
real channel[dsp.settings.channels];
for(unsigned n = 0; n < dsp.settings.channels; n++) {
real a = dsp.buffer.read(n, -1);
real b = dsp.buffer.read(n, -0);
real mu = fraction;
channel[n] = a * (1.0 - mu) + b * mu;
}
dsp.write(channel);
fraction += step;
}
dsp.buffer.rdoffset++;
fraction -= 1.0;
}
#endif

View File

@@ -0,0 +1,44 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct ResampleCosine : Resampler {
inline void setFrequency();
inline void clear();
inline void sample();
ResampleCosine(DSP &dsp) : Resampler(dsp) {}
real fraction;
real step;
};
void ResampleCosine::setFrequency() {
fraction = 0.0;
step = dsp.settings.frequency / frequency;
}
void ResampleCosine::clear() {
fraction = 0.0;
}
void ResampleCosine::sample() {
while(fraction <= 1.0) {
real channel[dsp.settings.channels];
for(unsigned n = 0; n < dsp.settings.channels; n++) {
real a = dsp.buffer.read(n, -1);
real b = dsp.buffer.read(n, -0);
real mu = fraction;
mu = (1.0 - cos(mu * 3.14159265)) / 2.0;
channel[n] = a * (1.0 - mu) + b * mu;
}
dsp.write(channel);
fraction += step;
}
dsp.buffer.rdoffset++;
fraction -= 1.0;
}
#endif

View File

@@ -0,0 +1,50 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct ResampleCubic : Resampler {
inline void setFrequency();
inline void clear();
inline void sample();
ResampleCubic(DSP &dsp) : Resampler(dsp) {}
real fraction;
real step;
};
void ResampleCubic::setFrequency() {
fraction = 0.0;
step = dsp.settings.frequency / frequency;
}
void ResampleCubic::clear() {
fraction = 0.0;
}
void ResampleCubic::sample() {
while(fraction <= 1.0) {
real channel[dsp.settings.channels];
for(unsigned n = 0; n < dsp.settings.channels; n++) {
real a = dsp.buffer.read(n, -3);
real b = dsp.buffer.read(n, -2);
real c = dsp.buffer.read(n, -1);
real d = dsp.buffer.read(n, -0);
real mu = fraction;
real A = d - c - a + b;
real B = a - b - A;
real C = c - a;
real D = b;
channel[n] = A * (mu * 3) + B * (mu * 2) + C * mu + D;
}
dsp.write(channel);
fraction += step;
}
dsp.buffer.rdoffset++;
fraction -= 1.0;
}
#endif

View File

@@ -0,0 +1,62 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct ResampleHermite : Resampler {
inline void setFrequency();
inline void clear();
inline void sample();
ResampleHermite(DSP &dsp) : Resampler(dsp) {}
real fraction;
real step;
};
void ResampleHermite::setFrequency() {
fraction = 0.0;
step = dsp.settings.frequency / frequency;
}
void ResampleHermite::clear() {
fraction = 0.0;
}
void ResampleHermite::sample() {
while(fraction <= 1.0) {
real channel[dsp.settings.channels];
for(unsigned n = 0; n < dsp.settings.channels; n++) {
real a = dsp.buffer.read(n, -3);
real b = dsp.buffer.read(n, -2);
real c = dsp.buffer.read(n, -1);
real d = dsp.buffer.read(n, -0);
const real tension = 0.0; //-1 = low, 0 = normal, +1 = high
const real bias = 0.0; //-1 = left, 0 = even, +1 = right
real mu1, mu2, mu3, m0, m1, a0, a1, a2, a3;
mu1 = fraction;
mu2 = mu1 * mu1;
mu3 = mu2 * mu1;
m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0;
m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0;
m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0;
m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0;
a0 = +2 * mu3 - 3 * mu2 + 1;
a1 = mu3 - 2 * mu2 + mu1;
a2 = mu3 - mu2;
a3 = -2 * mu3 + 3 * mu2;
channel[n] = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
}
dsp.write(channel);
fraction += step;
}
dsp.buffer.rdoffset++;
fraction -= 1.0;
}
#endif

View File

@@ -0,0 +1,600 @@
// If these types are changed to anything other than "float", you should comment out the SSE detection directives below
// so that the SSE code is not used.
typedef float resample_coeff_t; // note: sizeof(resample_coeff_t) must be == to a power of 2, and not larger than 16
typedef float resample_samp_t;
// ...but don't comment this single RESAMPLE_SSEREGPARM define out when disabling SSE.
#define RESAMPLE_SSEREGPARM
#if defined(__SSE__)
#define SINCRESAMPLE_USE_SSE 1
#ifndef __x86_64__
#undef RESAMPLE_SSEREGPARM
#define RESAMPLE_SSEREGPARM __attribute__((sseregparm))
#endif
#else
// TODO: altivec here
#endif
namespace ResampleUtility
{
inline void kaiser_window(double* io, int count, double beta);
inline void gen_sinc(double* out, int size, double cutoff, double kaiser);
inline void gen_sinc_os(double* out, int size, double cutoff, double kaiser);
inline void normalize(double* io, int size, double gain = 1.0);
inline void* make_aligned(void* ptr, unsigned boundary); // boundary must be a power of 2
}
class SincResampleHR
{
private:
inline void Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d);
inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM;
inline resample_samp_t read(void) RESAMPLE_SSEREGPARM;
inline bool output_avail(void);
private:
inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count);
unsigned ratio;
unsigned num_convolutions;
resample_coeff_t *coeffs;
std::vector<unsigned char> coeffs_mem;
// second half of ringbuffer should be copy of first half.
resample_samp_t *rb;
std::vector<unsigned char> rb_mem;
signed rb_readpos;
signed rb_writepos;
signed rb_in;
signed rb_eff_size;
friend class SincResample;
};
class SincResample
{
public:
enum
{
QUALITY_LOW = 0,
QUALITY_MEDIUM = 2,
QUALITY_HIGH = 4
};
inline SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality = QUALITY_HIGH);
inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM;
inline resample_samp_t read(void) RESAMPLE_SSEREGPARM;
inline bool output_avail(void);
private:
inline void Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min);
inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count) RESAMPLE_SSEREGPARM;
unsigned num_convolutions;
unsigned num_phases;
unsigned step_int;
double step_fract;
double input_pos_fract;
std::vector<resample_coeff_t *> coeffs; // Pointers into coeff_mem.
std::vector<unsigned char> coeff_mem;
std::vector<resample_samp_t> rb; // second half should be copy of first half.
signed rb_readpos;
signed rb_writepos;
signed rb_in;
bool hr_used;
SincResampleHR hr;
};
//
// Code:
//
//#include "resample.hpp"
#if 0
namespace bit
{
inline unsigned round(unsigned x) {
if((x & (x - 1)) == 0) return x;
while(x & (x - 1)) x &= x - 1;
return x << 1;
}
}
#endif
void SincResampleHR::Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d)
{
const unsigned align_boundary = 16;
std::vector<double> coeffs_tmp;
double cutoff; // 1.0 = f/2
ratio = ratio_arg;
//num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) + 1) &~ 1; // round up to be even
num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) | 1);
cutoff = (1.0 / ratio) - (d / num_convolutions);
//printf("%d %d %.20f\n", ratio, num_convolutions, cutoff);
assert(num_convolutions > ratio);
// Generate windowed sinc of POWER
coeffs_tmp.resize(num_convolutions);
//ResampleUtility::gen_sinc(&coeffs_tmp[0], num_convolutions, cutoff, beta);
ResampleUtility::gen_sinc_os(&coeffs_tmp[0], num_convolutions, cutoff, beta);
ResampleUtility::normalize(&coeffs_tmp[0], num_convolutions);
// Copy from coeffs_tmp to coeffs~
// We multiply many coefficients at a time in the mac loop, so make sure the last few that don't really
// exist are allocated, zero'd mem.
coeffs_mem.resize(((num_convolutions + 7) &~ 7) * sizeof(resample_coeff_t) + (align_boundary - 1));
coeffs = (resample_coeff_t *)ResampleUtility::make_aligned(&coeffs_mem[0], align_boundary);
for(unsigned i = 0; i < num_convolutions; i++)
coeffs[i] = coeffs_tmp[i];
rb_eff_size = nall::bit::round(num_convolutions * 2) >> 1;
rb_readpos = 0;
rb_writepos = 0;
rb_in = 0;
rb_mem.resize(rb_eff_size * 2 * sizeof(resample_samp_t) + (align_boundary - 1));
rb = (resample_samp_t *)ResampleUtility::make_aligned(&rb_mem[0], align_boundary);
}
inline bool SincResampleHR::output_avail(void)
{
return(rb_in >= (signed)num_convolutions);
}
inline void SincResampleHR::write(resample_samp_t sample)
{
assert(!output_avail());
rb[rb_writepos] = sample;
rb[rb_writepos + rb_eff_size] = sample;
rb_writepos = (rb_writepos + 1) & (rb_eff_size - 1);
rb_in++;
}
resample_samp_t SincResampleHR::mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count)
{
#if SINCRESAMPLE_USE_SSE
__m128 accum_veca[2] = { _mm_set1_ps(0), _mm_set1_ps(0) };
resample_samp_t accum;
for(unsigned c = 0; c < count; c += 8)
{
for(unsigned i = 0; i < 2; i++)
{
__m128 co[2];
__m128 w[2];
co[i] = _mm_load_ps(&coeff[c + i * 4]);
w[i] = _mm_load_ps(&wave[c + i * 4]);
w[i] = _mm_mul_ps(w[i], co[i]);
accum_veca[i] = _mm_add_ps(w[i], accum_veca[i]);
}
}
__m128 accum_vec = _mm_add_ps(accum_veca[0], accum_veca[1]); //_mm_add_ps(_mm_add_ps(accum_veca[0], accum_veca[1]), _mm_add_ps(accum_veca[2], accum_veca[3]));
accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6)));
accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6)));
_mm_store_ss(&accum, accum_vec);
return accum;
#else
resample_samp_t accum[4] = { 0, 0, 0, 0 };
for(unsigned c = 0; c < count; c+= 4)
{
accum[0] += wave[c + 0] * coeff[c + 0];
accum[1] += wave[c + 1] * coeff[c + 1];
accum[2] += wave[c + 2] * coeff[c + 2];
accum[3] += wave[c + 3] * coeff[c + 3];
}
return (accum[0] + accum[1]) + (accum[2] + accum[3]); // don't mess with parentheses(assuming compiler doesn't already, which it may...
#endif
}
resample_samp_t SincResampleHR::read(void)
{
assert(output_avail());
resample_samp_t ret;
ret = mac(&rb[rb_readpos], &coeffs[0], num_convolutions);
rb_readpos = (rb_readpos + ratio) & (rb_eff_size - 1);
rb_in -= ratio;
return ret;
}
SincResample::SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality)
{
const struct
{
double beta;
double d;
unsigned pn_nume;
unsigned phases_min;
} qtab[5] =
{
{ 5.658, 3.62, 4096, 4 },
{ 6.764, 4.32, 8192, 4 },
{ 7.865, 5.0, 16384, 8 },
{ 8.960, 5.7, 32768, 16 },
{ 10.056, 6.4, 65536, 32 }
};
// Sanity checks
assert(ceil(input_rate) > 0);
assert(ceil(output_rate) > 0);
assert(ceil(input_rate / output_rate) <= 1024);
assert(ceil(output_rate / input_rate) <= 1024);
// The simplistic number-of-phases calculation code doesn't work well enough for when desired_bandwidth is close to 1.0 and when
// upsampling.
assert(desired_bandwidth >= 0.25 && desired_bandwidth < 0.96);
assert(quality >= 0 && quality <= 4);
hr_used = false;
#if 1
// Round down to the nearest multiple of 4(so wave buffer remains aligned)
// It also adjusts the effective intermediate sampling rate up slightly, so that the upper frequencies below f/2
// aren't overly attenuated so much. In the future, we might want to do an FFT or something to choose the intermediate rate more accurately
// to virtually eliminate over-attenuation.
unsigned ioratio_rd = (unsigned)floor(input_rate / (output_rate * (1.0 + (1.0 - desired_bandwidth) / 2) )) & ~3;
if(ioratio_rd >= 8)
{
hr.Init(ioratio_rd, desired_bandwidth, qtab[quality].beta, qtab[quality].d); //10.056, 6.4);
hr_used = true;
input_rate /= ioratio_rd;
}
#endif
Init(input_rate, output_rate, desired_bandwidth, qtab[quality].beta, qtab[quality].d, qtab[quality].pn_nume, qtab[quality].phases_min);
}
void SincResample::Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min)
{
const unsigned max_mult_atatime = 8; // multiply "granularity". must be power of 2.
const unsigned max_mult_minus1 = (max_mult_atatime - 1);
const unsigned conv_alignment_bytes = 16; // must be power of 2
const double input_to_output_ratio = input_rate / output_rate;
const double output_to_input_ratio = output_rate / input_rate;
double cutoff; // 1.0 = input_rate / 2
std::vector<double> coeff_init_buffer;
// Round up num_convolutions to be even.
if(output_rate > input_rate)
num_convolutions = ((unsigned)ceil(d / (1.0 - desired_bandwidth)) + 1) & ~1;
else
num_convolutions = ((unsigned)ceil(d / (output_to_input_ratio * (1.0 - desired_bandwidth))) + 1) & ~1;
if(output_rate > input_rate) // Upsampling
cutoff = desired_bandwidth;
else // Downsampling
cutoff = output_to_input_ratio * desired_bandwidth;
// Round up to be even.
num_phases = (std::max<unsigned>(pn_nume / num_convolutions, phases_min) + 1) &~1;
// Adjust cutoff to account for the multiple phases.
cutoff = cutoff / num_phases;
assert((num_convolutions & 1) == 0);
assert((num_phases & 1) == 0);
// fprintf(stderr, "num_convolutions=%u, num_phases=%u, total expected coeff byte size=%lu\n", num_convolutions, num_phases,
// (long)((num_phases + 2) * ((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * sizeof(float) + conv_alignment_bytes));
coeff_init_buffer.resize(num_phases * num_convolutions);
coeffs.resize(num_phases + 1 + 1);
coeff_mem.resize((num_phases + 1 + 1) * ((num_convolutions + max_mult_minus1) &~ max_mult_minus1) * sizeof(resample_coeff_t) + conv_alignment_bytes);
// Assign aligned pointers into coeff_mem
{
resample_coeff_t *base_ptr = (resample_coeff_t *)ResampleUtility::make_aligned(&coeff_mem[0], conv_alignment_bytes);
for(unsigned phase = 0; phase < (num_phases + 1 + 1); phase++)
{
coeffs[phase] = base_ptr + (((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * phase);
}
}
ResampleUtility::gen_sinc(&coeff_init_buffer[0], num_phases * num_convolutions, cutoff, beta);
ResampleUtility::normalize(&coeff_init_buffer[0], num_phases * num_convolutions, num_phases);
// Reorder coefficients to allow for more efficient convolution.
for(int phase = -1; phase < ((int)num_phases + 1); phase++)
{
for(int conv = 0; conv < (int)num_convolutions; conv++)
{
double coeff;
if(phase == -1 && conv == 0)
coeff = 0;
else if(phase == (int)num_phases && conv == ((int)num_convolutions - 1))
coeff = 0;
else
coeff = coeff_init_buffer[conv * num_phases + phase];
coeffs[phase + 1][conv] = coeff;
}
}
// Free a bit of mem
coeff_init_buffer.resize(0);
step_int = floor(input_to_output_ratio);
step_fract = input_to_output_ratio - step_int;
input_pos_fract = 0;
// Do NOT use rb.size() later in the code, since it'll include the padding.
// We should only need one "max_mult_minus1" here, not two, since it won't matter if it over-reads(due to doing "max_mult_atatime" multiplications at a time
// rather than just 1, in which case this over-read wouldn't happen), from the first half into the duplicated half,
// since those corresponding coefficients will be zero anyway; this is just to handle the case of reading off the end of the duplicated half to
// prevent illegal memory accesses.
rb.resize(num_convolutions * 2 + max_mult_minus1);
rb_readpos = 0;
rb_writepos = 0;
rb_in = 0;
}
resample_samp_t SincResample::mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count)
{
resample_samp_t accum = 0;
#if SINCRESAMPLE_USE_SSE
__m128 accum_vec_a[2] = { _mm_set1_ps(0), _mm_set1_ps(0) };
__m128 accum_vec_b[2] = { _mm_set1_ps(0), _mm_set1_ps(0) };
for(unsigned c = 0; c < count; c += 8) //8) //4)
{
__m128 coeff_a[2];
__m128 coeff_b[2];
__m128 w[2];
__m128 result_a[2], result_b[2];
for(unsigned i = 0; i < 2; i++)
{
coeff_a[i] = _mm_load_ps(&coeffs_a[c + (i * 4)]);
coeff_b[i] = _mm_load_ps(&coeffs_b[c + (i * 4)]);
w[i] = _mm_loadu_ps(&wave[c + (i * 4)]);
result_a[i] = _mm_mul_ps(coeff_a[i], w[i]);
result_b[i] = _mm_mul_ps(coeff_b[i], w[i]);
accum_vec_a[i] = _mm_add_ps(result_a[i], accum_vec_a[i]);
accum_vec_b[i] = _mm_add_ps(result_b[i], accum_vec_b[i]);
}
}
__m128 accum_vec, av_a, av_b;
__m128 mult_a_vec = _mm_set1_ps(1.0 - ffract);
__m128 mult_b_vec = _mm_set1_ps(ffract);
av_a = _mm_mul_ps(mult_a_vec, /*accum_vec_a[0]);*/ _mm_add_ps(accum_vec_a[0], accum_vec_a[1]));
av_b = _mm_mul_ps(mult_b_vec, /*accum_vec_b[0]);*/ _mm_add_ps(accum_vec_b[0], accum_vec_b[1]));
accum_vec = _mm_add_ps(av_a, av_b);
accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6)));
accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6)));
_mm_store_ss(&accum, accum_vec);
#else
resample_coeff_t mult_a = 1.0 - ffract;
resample_coeff_t mult_b = ffract;
for(unsigned c = 0; c < count; c += 4)
{
accum += wave[c + 0] * (coeffs_a[c + 0] * mult_a + coeffs_b[c + 0] * mult_b);
accum += wave[c + 1] * (coeffs_a[c + 1] * mult_a + coeffs_b[c + 1] * mult_b);
accum += wave[c + 2] * (coeffs_a[c + 2] * mult_a + coeffs_b[c + 2] * mult_b);
accum += wave[c + 3] * (coeffs_a[c + 3] * mult_a + coeffs_b[c + 3] * mult_b);
}
#endif
return accum;
}
inline bool SincResample::output_avail(void)
{
return(rb_in >= (int)num_convolutions);
}
resample_samp_t SincResample::read(void)
{
assert(output_avail());
double phase = input_pos_fract * num_phases - 0.5;
signed phase_int = (signed)floor(phase);
double phase_fract = phase - phase_int;
unsigned phase_a = num_phases - 1 - phase_int;
unsigned phase_b = phase_a - 1;
resample_samp_t ret;
ret = mac(&rb[rb_readpos], &coeffs[phase_a + 1][0], &coeffs[phase_b + 1][0], phase_fract, num_convolutions);
unsigned int_increment = step_int;
input_pos_fract += step_fract;
int_increment += floor(input_pos_fract);
input_pos_fract -= floor(input_pos_fract);
rb_readpos = (rb_readpos + int_increment) % num_convolutions;
rb_in -= int_increment;
return ret;
}
inline void SincResample::write(resample_samp_t sample)
{
assert(!output_avail());
if(hr_used)
{
hr.write(sample);
if(hr.output_avail())
{
sample = hr.read();
}
else
{
return;
}
}
rb[rb_writepos + 0 * num_convolutions] = sample;
rb[rb_writepos + 1 * num_convolutions] = sample;
rb_writepos = (rb_writepos + 1) % num_convolutions;
rb_in++;
}
void ResampleUtility::kaiser_window( double* io, int count, double beta)
{
int const accuracy = 24; //16; //12;
double* end = io + count;
double beta2 = beta * beta * (double) -0.25;
double to_fract = beta2 / ((double) count * count);
double i = 0;
double rescale = 0; // Doesn't need an initializer, to shut up gcc
for ( ; io < end; ++io, i += 1 )
{
double x = i * i * to_fract - beta2;
double u = x;
double k = x + 1;
double n = 2;
do
{
u *= x / (n * n);
n += 1;
k += u;
}
while ( k <= u * (1 << accuracy) );
if ( !i )
rescale = 1 / k; // otherwise values get large
*io *= k * rescale;
}
}
void ResampleUtility::gen_sinc(double* out, int size, double cutoff, double kaiser)
{
assert( size % 2 == 0 ); // size must be even
int const half_size = size / 2;
double* const mid = &out [half_size];
// Generate right half of sinc
for ( int i = 0; i < half_size; i++ )
{
double angle = (i * 2 + 1) * (M_PI / 2);
mid [i] = sin( angle * cutoff ) / angle;
}
kaiser_window( mid, half_size, kaiser );
// Mirror for left half
for ( int i = 0; i < half_size; i++ )
out [i] = mid [half_size - 1 - i];
}
void ResampleUtility::gen_sinc_os(double* out, int size, double cutoff, double kaiser)
{
assert( size % 2 == 1); // size must be odd
for(int i = 0; i < size; i++)
{
if(i == (size / 2))
out[i] = 2 * M_PI * (cutoff / 2); //0.078478; //1.0; //sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2));
else
out[i] = sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2));
// out[i] *= 0.3635819 - 0.4891775 * cos(2 * M_PI * i / (size - 1)) + 0.1365995 * cos(4 * M_PI * i / (size - 1)) - 0.0106411 * cos(6 * M_PI * i / (size - 1));
//0.42 - 0.5 * cos(2 * M_PI * i / (size - 1)) + 0.08 * cos(4 * M_PI * i / (size - 1));
// printf("%d %f\n", i, out[i]);
}
kaiser_window(&out[size / 2], size / 2 + 1, kaiser);
// Mirror for left half
for ( int i = 0; i < size / 2; i++ )
out [i] = out [size - 1 - i];
}
void ResampleUtility::normalize(double* io, int size, double gain)
{
double sum = 0;
for ( int i = 0; i < size; i++ )
sum += io [i];
double scale = gain / sum;
for ( int i = 0; i < size; i++ )
io [i] *= scale;
}
void* ResampleUtility::make_aligned(void* ptr, unsigned boundary)
{
unsigned char* null_ptr = (unsigned char *)NULL;
unsigned char* uc_ptr = (unsigned char *)ptr;
uc_ptr += (boundary - ((uc_ptr - null_ptr) & (boundary - 1))) & (boundary - 1);
//while((uc_ptr - null_ptr) & (boundary - 1))
// uc_ptr++;
//printf("%16llx %16llx\n", (unsigned long long)ptr, (unsigned long long)uc_ptr);
assert((uc_ptr - (unsigned char *)ptr) < boundary && (uc_ptr >= (unsigned char *)ptr));
return uc_ptr;
}

View File

@@ -0,0 +1,43 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct ResampleLinear : Resampler {
inline void setFrequency();
inline void clear();
inline void sample();
ResampleLinear(DSP &dsp) : Resampler(dsp) {}
real fraction;
real step;
};
void ResampleLinear::setFrequency() {
fraction = 0.0;
step = dsp.settings.frequency / frequency;
}
void ResampleLinear::clear() {
fraction = 0.0;
}
void ResampleLinear::sample() {
while(fraction <= 1.0) {
real channel[dsp.settings.channels];
for(unsigned n = 0; n < dsp.settings.channels; n++) {
real a = dsp.buffer.read(n, -1);
real b = dsp.buffer.read(n, -0);
real mu = fraction;
channel[n] = a * (1.0 - mu) + b * mu;
}
dsp.write(channel);
fraction += step;
}
dsp.buffer.rdoffset++;
fraction -= 1.0;
}
#endif

View File

@@ -0,0 +1,43 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct ResampleNearest : Resampler {
inline void setFrequency();
inline void clear();
inline void sample();
ResampleNearest(DSP &dsp) : Resampler(dsp) {}
real fraction;
real step;
};
void ResampleNearest::setFrequency() {
fraction = 0.0;
step = dsp.settings.frequency / frequency;
}
void ResampleNearest::clear() {
fraction = 0.0;
}
void ResampleNearest::sample() {
while(fraction <= 1.0) {
real channel[dsp.settings.channels];
for(unsigned n = 0; n < dsp.settings.channels; n++) {
real a = dsp.buffer.read(n, -1);
real b = dsp.buffer.read(n, -0);
real mu = fraction;
channel[n] = mu < 0.5 ? a : b;
}
dsp.write(channel);
fraction += step;
}
dsp.buffer.rdoffset++;
fraction -= 1.0;
}
#endif

View File

@@ -0,0 +1,54 @@
#ifdef NALL_DSP_INTERNAL_HPP
#include "lib/sinc.hpp"
struct ResampleSinc : Resampler {
inline void setFrequency();
inline void clear();
inline void sample();
inline ResampleSinc(DSP &dsp);
private:
inline void remakeSinc();
SincResample *sinc_resampler[8];
};
void ResampleSinc::setFrequency() {
remakeSinc();
}
void ResampleSinc::clear() {
remakeSinc();
}
void ResampleSinc::sample() {
for(unsigned c = 0; c < dsp.settings.channels; c++) {
sinc_resampler[c]->write(dsp.buffer.read(c));
}
if(sinc_resampler[0]->output_avail()) {
do {
for(unsigned c = 0; c < dsp.settings.channels; c++) {
dsp.output.write(c) = sinc_resampler[c]->read();
}
dsp.output.wroffset++;
} while(sinc_resampler[0]->output_avail());
}
dsp.buffer.rdoffset++;
}
ResampleSinc::ResampleSinc(DSP &dsp) : Resampler(dsp) {
for(unsigned n = 0; n < 8; n++) sinc_resampler[n] = 0;
}
void ResampleSinc::remakeSinc() {
assert(dsp.settings.channels < 8);
for(unsigned c = 0; c < dsp.settings.channels; c++) {
if(sinc_resampler[c]) delete sinc_resampler[c];
sinc_resampler[c] = new SincResample(dsp.settings.frequency, frequency, 0.85, SincResample::QUALITY_HIGH);
}
}
#endif

50
bsnes/nall/dsp/settings.hpp Executable file
View File

@@ -0,0 +1,50 @@
#ifdef NALL_DSP_INTERNAL_HPP
void DSP::setChannels(unsigned channels) {
assert(channels > 0);
buffer.setChannels(channels);
output.setChannels(channels);
settings.channels = channels;
}
void DSP::setPrecision(unsigned precision) {
settings.precision = precision;
settings.intensity = 1 << (settings.precision - 1);
settings.intensityInverse = 1.0 / settings.intensity;
}
void DSP::setFrequency(real frequency) {
settings.frequency = frequency;
resampler->setFrequency();
}
void DSP::setVolume(real volume) {
settings.volume = volume;
}
void DSP::setBalance(real balance) {
settings.balance = balance;
}
void DSP::setResampler(ResampleEngine engine) {
if(resampler) delete resampler;
switch(engine) {
case ResampleEngine::Nearest: resampler = new ResampleNearest(*this); return;
case ResampleEngine::Linear: resampler = new ResampleLinear (*this); return;
case ResampleEngine::Cosine: resampler = new ResampleCosine (*this); return;
case ResampleEngine::Cubic: resampler = new ResampleCubic (*this); return;
case ResampleEngine::Hermite: resampler = new ResampleHermite(*this); return;
case ResampleEngine::Average: resampler = new ResampleAverage(*this); return;
case ResampleEngine::Sinc: resampler = new ResampleSinc (*this); return;
}
throw;
}
void DSP::setResamplerFrequency(real frequency) {
resampler->frequency = frequency;
resampler->setFrequency();
}
#endif

View File

@@ -1,7 +1,9 @@
#ifndef NALL_ENDIAN_HPP
#define NALL_ENDIAN_HPP
#if !defined(ARCH_MSB)
#include <nall/intrinsics.hpp>
#if defined(ENDIAN_LSB)
//little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201
#define order_lsb2(a,b) a,b
#define order_lsb3(a,b,c) a,b,c
@@ -17,7 +19,7 @@
#define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a
#define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a
#define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a
#else
#elif defined(ENDIAN_MSB)
//big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304
#define order_lsb2(a,b) b,a
#define order_lsb3(a,b,c) c,b,a
@@ -33,6 +35,8 @@
#define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f
#define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g
#define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h
#else
#error "Unknown endian. Please specify in nall/intrinsics.hpp"
#endif
#endif

View File

@@ -23,6 +23,7 @@ namespace nall {
enum class time : unsigned { create, modify, access };
static bool read(const string &filename, uint8_t *&data, unsigned &size) {
data = 0;
file fp;
if(fp.open(filename, mode::read) == false) return false;
size = fp.size();
@@ -32,6 +33,10 @@ namespace nall {
return true;
}
static bool read(const string &filename, const uint8_t *&data, unsigned &size) {
return file::read(filename, (uint8_t*&)data, size);
}
static bool write(const string &filename, const uint8_t *data, unsigned size) {
file fp;
if(fp.open(filename, mode::write) == false) return false;
@@ -129,12 +134,12 @@ namespace nall {
file_offset = req_offset;
}
int offset() {
int offset() const {
if(!fp) return -1; //file not open
return file_offset;
}
int size() {
int size() const {
if(!fp) return -1; //file not open
return file_size;
}
@@ -189,7 +194,7 @@ namespace nall {
}
}
bool open() {
bool open() const {
return fp;
}

View File

@@ -1,6 +1,7 @@
#ifndef NALL_FILEMAP_HPP
#define NALL_FILEMAP_HPP
#include <nall/file.hpp>
#include <nall/stdint.hpp>
#include <nall/windows/utf8.hpp>
@@ -47,6 +48,12 @@ namespace nall {
}
bool p_open(const char *filename, mode mode_) {
if(file::exists(filename) && file::size(filename) == 0) {
p_handle = 0;
p_size = 0;
return true;
}
int desired_access, creation_disposition, flprotect, map_access;
switch(mode_) {
@@ -133,6 +140,12 @@ namespace nall {
}
bool p_open(const char *filename, mode mode_) {
if(file::exists(filename) && file::size(filename) == 0) {
p_handle = 0;
p_size = 0;
return true;
}
int open_flags, mmap_flags;
switch(mode_) {

View File

@@ -1,18 +0,0 @@
#ifndef NALL_FOREACH_HPP
#define NALL_FOREACH_HPP
#include <type_traits>
#include <nall/concept.hpp>
#undef foreach
#define foreach2(iter, object) foreach3(iter, object, foreach_counter)
#define foreach3(iter, object, foreach_counter) \
for(unsigned foreach_counter = 0, foreach_limit = container_size(object), foreach_once = 0, foreach_broken = 0; foreach_counter < foreach_limit && foreach_broken == 0; foreach_counter++, foreach_once = 0) \
for(auto &iter = object[foreach_counter]; foreach_once == 0 && (foreach_broken = 1); foreach_once++, foreach_broken = 0)
#define foreach_impl(...) foreach_decl(__VA_ARGS__, foreach3(__VA_ARGS__), foreach2(__VA_ARGS__), foreach_too_few_arguments)
#define foreach_decl(_1, _2, _3, N, ...) N
#define foreach(...) foreach_impl(__VA_ARGS__)
#endif

View File

@@ -36,19 +36,19 @@ namespace nall {
public:
operator bool() const { return callback; }
R operator()(P... p) const { return (*callback)(std::forward<P>(p)...); }
void reset() { if(callback) { delete callback; callback = 0; } }
void reset() { if(callback) { delete callback; callback = nullptr; } }
function& operator=(const function &source) {
if(this != &source) {
if(callback) { delete callback; callback = 0; }
if(callback) { delete callback; callback = nullptr; }
if(source.callback) callback = source.callback->copy();
}
return *this;
}
function(const function &source) : callback(0) { operator=(source); }
function() : callback(0) {}
function(void *function) : callback(0) { if(function) callback = new global((R (*)(P...))function); }
function(const function &source) : callback(nullptr) { operator=(source); }
function() : callback(nullptr) {}
function(void *function) : callback(nullptr) { if(function) callback = new global((R (*)(P...))function); }
function(R (*function)(P...)) { callback = new global(function); }
template<typename C> function(R (C::*function)(P...), C *object) { callback = new member<C>(function, object); }
template<typename C> function(R (C::*function)(P...) const, C *object) { callback = new member<C>((R (C::*)(P...))function, object); }

View File

@@ -5,8 +5,8 @@ namespace nall {
class GameBoyCartridge {
public:
string xml;
inline GameBoyCartridge(const uint8_t *data, unsigned size);
string markup;
inline GameBoyCartridge(uint8_t *data, unsigned size);
//private:
struct Information {
@@ -21,8 +21,8 @@ public:
} info;
};
GameBoyCartridge::GameBoyCartridge(const uint8_t *romdata, unsigned romsize) {
xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
GameBoyCartridge::GameBoyCartridge(uint8_t *romdata, unsigned romsize) {
markup = "";
if(romsize < 0x4000) return;
info.mapper = "unknown";
@@ -34,6 +34,20 @@ GameBoyCartridge::GameBoyCartridge(const uint8_t *romdata, unsigned romsize) {
info.romsize = 0;
info.ramsize = 0;
unsigned base = romsize - 0x8000;
if(romdata[base + 0x0104] == 0xce && romdata[base + 0x0105] == 0xed
&& romdata[base + 0x0106] == 0x66 && romdata[base + 0x0107] == 0x66
&& romdata[base + 0x0108] == 0xcc && romdata[base + 0x0109] == 0x0d
&& romdata[base + 0x0147] >= 0x0b && romdata[base + 0x0147] <= 0x0d
) {
//MMM01 stores header at bottom of image
//flip this around for consistency with all other mappers
uint8_t header[0x8000];
memcpy(header, romdata + base, 0x8000);
memmove(romdata + 0x8000, romdata, romsize - 0x8000);
memcpy(romdata, header, 0x8000);
}
switch(romdata[0x0147]) {
case 0x00: info.mapper = "none"; break;
case 0x01: info.mapper = "MBC1"; break;
@@ -86,18 +100,26 @@ GameBoyCartridge::GameBoyCartridge(const uint8_t *romdata, unsigned romsize) {
if(info.mapper == "MBC2") info.ramsize = 512; //512 x 4-bit
xml.append("<cartridge mapper='", info.mapper, "'");
if(info.rtc) xml.append(" rtc='true'");
if(info.rumble) xml.append(" rumble='true'");
xml.append(">\n");
markup.append(
"<?xml version='1.0' encoding='UTF-8'?>\n",
"<cartridge mapper='", info.mapper, "' rtc='", info.rtc, "' rumble='", info.rumble, "'>\n",
" <rom size='0x", hex(romsize), "'/>\n");
if(info.ramsize > 0) markup.append(
" <ram size='0x", hex(info.ramsize), "' battery='", info.battery, "'/>\n");
markup.append(
"</cartridge>\n");
xml.append(" <rom size='", hex(romsize), "'/>\n"); //TODO: trust/check info.romsize?
/*
markup.append("cartridge mapper=", info.mapper);
if(info.rtc) markup.append(" rtc");
if(info.rumble) markup.append(" rumble");
markup.append("\n");
markup.append("\t" "rom size=", hex(romsize), "\n"); //TODO: trust/check info.romsize?
if(info.ramsize > 0)
xml.append(" <ram size='", hex(info.ramsize), "' battery='", info.battery, "'/>\n");
xml.append("</cartridge>\n");
xml.transform("'", "\"");
markup.append("\t" "ram size=", hex(info.ramsize), info.battery ? " non-volatile\n" : "\n");
*/
}
}

View File

@@ -11,11 +11,11 @@ struct gzip {
uint8_t *data;
unsigned size;
bool decompress(const string &filename);
bool decompress(const uint8_t *data, unsigned size);
inline bool decompress(const string &filename);
inline bool decompress(const uint8_t *data, unsigned size);
gzip();
~gzip();
inline gzip();
inline ~gzip();
};
bool gzip::decompress(const string &filename) {
@@ -75,7 +75,7 @@ bool gzip::decompress(const uint8_t *data, unsigned size) {
return inflate(this->data, this->size, data + p, size - p - 8);
}
gzip::gzip() : data(0) {
gzip::gzip() : data(nullptr) {
}
gzip::~gzip() {

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