mirror of
https://github.com/libretro/neocd_libretro.git
synced 2024-11-23 00:19:40 +00:00
Initial commit.
This commit is contained in:
commit
48f9d59d9f
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build
|
||||
distclean
|
||||
.clang-format
|
115
CMakeLists.txt
Normal file
115
CMakeLists.txt
Normal file
@ -0,0 +1,115 @@
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
|
||||
# Path to CMake scripts
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmakescripts")
|
||||
|
||||
# Avoid having empty buildtype
|
||||
set(CMAKE_BUILD_TYPE_INIT "Release")
|
||||
|
||||
# Project name
|
||||
project(neocd_libretro)
|
||||
set(PROJECT_NAME neocd_libretro)
|
||||
|
||||
# Set default locations
|
||||
set(CL_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/cmake-build-${CMAKE_BUILD_TYPE}/output)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CL_OUTPUT_DIRECTORY})
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CL_OUTPUT_DIRECTORY})
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CL_OUTPUT_DIRECTORY})
|
||||
|
||||
# Check if the compiler can handle C++11
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Add the current directory to includes
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
|
||||
|
||||
################################################################
|
||||
# Compiler options #
|
||||
# Uncomment one line suitable for your system or make your own #
|
||||
################################################################
|
||||
|
||||
set(BASE_CXX_OPTIONS "-std=c++11 -Ofast -fomit-frame-pointer -fno-exceptions -fno-rtti")
|
||||
|
||||
# Generic x64 options
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=x86-64 -mtune=generic ${BASE_CXX_OPTIONS}")
|
||||
|
||||
# Generic x64 options, generate profile guided optimization files
|
||||
# set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=x86-64 -mtune=generic ${BASE_CXX_OPTIONS} -fprofile-generate=${CL_OUTPUT_DIRECTORY}")
|
||||
|
||||
# Generic x64 options, use profile guided optimization files
|
||||
# set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=x86-64 -mtune=generic ${BASE_CXX_OPTIONS} -fprofile-use=${CL_OUTPUT_DIRECTORY}")
|
||||
|
||||
# All architectures, optimized for the machine NeoCD is compiled on
|
||||
# set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=native ${BASE_CXX_OPTIONS}")
|
||||
|
||||
# Linker options
|
||||
if (CMAKE_BUILD_TYPE MATCHES Release)
|
||||
set(LINK_OPTIONS -s)
|
||||
endif()
|
||||
|
||||
# Library path
|
||||
#set(CMAKE_LDFLAGS "${CMAKE_LDFLAGS} -L. ")
|
||||
|
||||
# Define the CXX sources
|
||||
set ( CXX_SRCS
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/musashi/m68kopdm.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/z80/z80daisy.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/musashi/m68kopac.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/musashi/m68kcpu.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/video.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/timergroup.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/timeprofiler.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/musashi/m68kopnz.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/oggfile.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/neogeocd.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/z80intf.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/wavfile.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/flacfile.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/lc8951.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/timer.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/m68kintf.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory_mapped.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/input.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory_switches.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/musashi/m68kops.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/cdrom.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/datapacker.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/libretro.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/audio.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/ym/ym2610.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory_backupram.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory_cdintf.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory_z80comm.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/z80/z80.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory_video.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory_input.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/memory_paletteram.cpp
|
||||
)
|
||||
|
||||
# Find the required libraries
|
||||
FIND_PACKAGE(FLAC REQUIRED)
|
||||
FIND_PACKAGE(Ogg REQUIRED)
|
||||
FIND_PACKAGE(Vorbis REQUIRED)
|
||||
FIND_PACKAGE(Threads REQUIRED)
|
||||
|
||||
INCLUDE_DIRECTORIES(
|
||||
${FLAC_INCLUDE_DIR}
|
||||
${OGG_INCLUDE_DIR}
|
||||
${VORBIS_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
# Add the libraries to the link options
|
||||
set(LINK_OPTIONS ${LINK_OPTIONS} Threads::Threads ${FLAC_LIBRARY} ${VORBIS_LIBRARY} ${OGG_LIBRARY} ${VORBISFILE_LIBRARY})
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED ${CXX_SRCS} ${C_SRCS})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_OPTIONS})
|
||||
|
||||
message("")
|
||||
message("Configuration Summary")
|
||||
message("---------------------")
|
||||
message("CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||
message("CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
message("LINK_OPTIONS: ${LINK_OPTIONS}")
|
||||
message("")
|
167
LICENSE.md
Normal file
167
LICENSE.md
Normal file
@ -0,0 +1,167 @@
|
||||
http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
116
README.md
Normal file
116
README.md
Normal file
@ -0,0 +1,116 @@
|
||||
# NeoCD-Libretro
|
||||
|
||||
## Introduction
|
||||
|
||||
NeoCD-Libretro is a complete rewrite of NeoCD from scratch in modern C++11. It is designed with accuracy and portability in mind rather than being all about speed like the the older versions. The goal is also to document all I know about the platform in the source code so other emulator authors can make their own implementations.
|
||||
|
||||
What is different?
|
||||
|
||||
* It's accurate. A lot more. As a result it uses more CPU power than older versions but requirements are still modest:
|
||||
NeoCD runs perfect on Raspberry Pi 3 for example.
|
||||
* It's implemented as a libretro core which allows it to be used everywhere, at home or on the go :)
|
||||
* The CD-ROM drive is now emulated at hardware level.
|
||||
* Supports savestates
|
||||
* Supports rewind
|
||||
|
||||
## Credits
|
||||
|
||||
NeoCD would not have been possible without the following people generously sharing their code:
|
||||
|
||||
* Karl Stenerud - [Musashi 68000 Emulation Core](https://github.com/kstenerud/Musashi)
|
||||
* Juergen Buchmueller - Z80 Emulation Core
|
||||
* Jarek Burczynski & Tatsuyuki Satoh - YM2610 Emulation Core
|
||||
* [The MAME Development Team](http://www.mamedev.org/) - MAME, an invaluable source of knowledge about arcade machines.
|
||||
|
||||
## Installation
|
||||
|
||||
### Core files
|
||||
|
||||
Copy the `libneocd_libretro` library to `RetroArch/cores`.
|
||||
|
||||
### The INFO file (cosmetic)
|
||||
|
||||
Copy `neocd_libretro.info` to folder `RetroArch/info`
|
||||
|
||||
#### Additional DLL files (Windows only)
|
||||
|
||||
NeoCD requires two DLL libraries not found in the standard distribution of RetroArch:
|
||||
|
||||
* `libFLAC-8.dll`
|
||||
* `libvorbisfile-3.dll`
|
||||
|
||||
Copy them somewhere in your path or directly into RetroArch's binary directory
|
||||
|
||||
|
||||
### Required BIOS Files
|
||||
|
||||
To function NeoCD need a BIOS from a Front Loading, Top Loading or CDZ machine. The BIOS files should be installed in a `neocd` folder under RetroArch's system folder.
|
||||
|
||||
The needed files are:
|
||||
|
||||
|Description | Filename | SHA1 |
|
||||
|------------|-----------|------------------------------------------|
|
||||
| Y Zoom ROM | ng-lo.rom | 2b1c719531dac9bb503f22644e6e4236b91e7cfc |
|
||||
|
||||
> **📓 Note:** You need at least one in the following table. If several BIOSes are available, it will be possible to choose which to run in the Core Options Menu.
|
||||
The files will be automatically byte swapped if needed.
|
||||
|
||||
| Description | Filename | SHA1 |
|
||||
|----------------------------|--------------|------------------------------------------|
|
||||
| Front Loader BIOS | neocd_f.rom | a5f4a7a627b3083c979f6ebe1fabc5d2df6d083b |
|
||||
| Front Loader BIOS (SMKDAN) | neocd_sf.rom | c99c44a43bded1bff4570b30b74975601bd3f94e |
|
||||
| Top Loader BIOS | neocd_t.rom | cc92b54a18a8bff6e595aabe8e5c360ba9e62eb5 |
|
||||
| Top Loader BIOS (SMKDAN) | neocd_st.rom | d463b3a322b9674f9e227a21e43898019ce0e642 |
|
||||
| CDZ BIOS | neocd_z.rom | b0f1c4fa8d4492a04431805f6537138b842b549f |
|
||||
| CDZ BIOS (SMKDAN) | neocd_sz.rom | 41ca1c031b844a46387be783ac862c76e65afbb3 |
|
||||
|
||||
| Description | Filename | Byte Swapped SHA1 |
|
||||
|----------------------------|--------------|------------------------------------------|
|
||||
| Front Loader BIOS | neocd_f.rom | 53bc1f283cdf00fa2efbb79f2e36d4c8038d743a |
|
||||
| Front Loader BIOS (SMKDAN) | neocd_sf.rom | 4a94719ee5d0e3f2b981498f70efc1b8f1cef325 |
|
||||
| Top Loader BIOS | neocd_t.rom | 235f4d1d74364415910f73c10ae5482d90b4274f |
|
||||
| Top Loader BIOS (SMKDAN) | neocd_st.rom | 19729b51bdab60c42aafef6e20ea9234c7eb8410 |
|
||||
| CDZ BIOS | neocd_z.rom | 7bb26d1e5d1e930515219cb18bcde5b7b23e2eda |
|
||||
| CDZ BIOS (SMKDAN) | neocd_sz.rom | 6a947457031dd3a702a296862446d7485aa89dbb |
|
||||
|
||||
### CD Images
|
||||
|
||||
In the era of modern computers and portable devices, CD-ROMs are no longer convenient. Additionally I believe it is not possible to read the TOC of protected games without special drivers. As a result, NeoCD now exclusively run using CD-ROM images.
|
||||
|
||||
> **📓 Note:** The copy protection mechanism is now emulated and functional. You will need a CD image with correct TOC information or patched images. The easiest way to create CD Images is probably to use **CloneCD** type software. If you find yourself unable to image your games correctly, there are archives with correct CUE files for all known games floating around the net. To tell if a copy protected CD image is correctly made open the CUE file, track 01 should have something very special about it.
|
||||
|
||||
NeoCD accepts as input a cue sheet file (CUE). The image can be either of "single file" type (CUE, BIN) or "multiple files" type (CUE,ISO,[WAV/FLAC/OGG]).
|
||||
|
||||
> **🎶 Supported audio formats are:** Wave (.wav), FLAC (.flac) or Ogg Vorbis (.ogg)
|
||||
|
||||
### The Core Options Menu
|
||||
|
||||
* **Region:** Change your Neo Geo CD's region. (changing this will reset the machine)
|
||||
* **BIOS Select:** Select the BIOS to use here if you have several (changing this will reset the machine)
|
||||
* **CD Speed Hack:** This will replace the BIOS CD-ROM busy loop with a STOP instruction, making emulation more efficient (optional, changing this will reset the machine)
|
||||
* **Skip CD Loading:** Settings this to ON makes the emulator auto fast forward CD loading sequences.
|
||||
|
||||
## For Developers
|
||||
|
||||
### Project Dependencies
|
||||
* A C++11 compiler
|
||||
* CMake > 3.0
|
||||
* libFLAC
|
||||
* libogg
|
||||
* libvorbis
|
||||
* MSYS (Windows)
|
||||
|
||||
The project uses custom cmake finders in the folder `cmakescripts` to locate the libraries.
|
||||
### Compiling
|
||||
* Make sure the development packages for libFLAC, libogg and libvorbis are installed.
|
||||
* Eventually, edit the CFLAGS in CMakeLists.txt to suit your platform (Raspberry Pi...)
|
||||
* Invoke CMake: `cmake -G "Unix Makefiles" .` or `cmake -G "MSYS Makefiles" .` (Windows)
|
||||
* If all went well, build: `make -j 4`
|
||||
* Copy the resulting library in `RetroArch/cores`
|
||||
* Done! (see the user section for the rest)
|
||||
|
||||
## Tested platforms
|
||||
|
||||
* x64 / Windows / GCC 8.2
|
||||
* x64 / Arch Linux / GCC 8.2
|
||||
* Raspberry Pi 3 / Arch Linux / GCC 8.2
|
42
cmakescripts/FindFLAC.cmake
Normal file
42
cmakescripts/FindFLAC.cmake
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
find_path(FLAC_INCLUDE_DIR FLAC/all.h
|
||||
HINTS
|
||||
ENV FLAC_DIR
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(EXTRA_PATH_SUFFIX x64)
|
||||
else()
|
||||
set(EXTRA_PATH_SUFFIX x86)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (BUILD_STATIC)
|
||||
find_library(FLAC_LIBRARY
|
||||
NAMES libFLAC.a FLAC FLAC-8 libFLAC-8
|
||||
HINTS
|
||||
ENV FLAC_DIR
|
||||
PATH_SUFFIXES lib ${EXTRA_PATH_SUFFIX}
|
||||
)
|
||||
else (BUILD_STATIC)
|
||||
find_library(FLAC_LIBRARY
|
||||
NAMES FLAC FLAC-8 libFLAC-8
|
||||
HINTS
|
||||
ENV FLAC_DIR
|
||||
PATH_SUFFIXES lib ${EXTRA_PATH_SUFFIX}
|
||||
)
|
||||
endif (BUILD_STATIC)
|
||||
|
||||
if(FLAC_LIBRARY)
|
||||
set(FLAC_VERSION 1)
|
||||
endif()
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set FLAC_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(FLAC REQUIRED_VARS FLAC_LIBRARY FLAC_INCLUDE_DIR
|
||||
VERSION_VAR FLAC_VERSION )
|
||||
|
||||
mark_as_advanced(FLAC_INCLUDE_DIR FLAC_LIBRARY)
|
42
cmakescripts/FindOgg.cmake
Normal file
42
cmakescripts/FindOgg.cmake
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
find_path(OGG_INCLUDE_DIR ogg/ogg.h
|
||||
HINTS
|
||||
ENV OGG_DIR
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(EXTRA_PATH_SUFFIX x64)
|
||||
else()
|
||||
set(EXTRA_PATH_SUFFIX x86)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (BUILD_STATIC)
|
||||
find_library(OGG_LIBRARY
|
||||
NAMES libogg.a ogg ogg-0 libogg-0
|
||||
HINTS
|
||||
ENV OGG_DIR
|
||||
PATH_SUFFIXES lib ${EXTRA_PATH_SUFFIX}
|
||||
)
|
||||
else (BUILD_STATIC)
|
||||
find_library(OGG_LIBRARY
|
||||
NAMES ogg ogg-0 libogg-0
|
||||
HINTS
|
||||
ENV OGG_DIR
|
||||
PATH_SUFFIXES lib ${EXTRA_PATH_SUFFIX}
|
||||
)
|
||||
endif (BUILD_STATIC)
|
||||
|
||||
if(OGG_LIBRARY)
|
||||
set(OGG_VERSION 1)
|
||||
endif()
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set OGG_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(OGG REQUIRED_VARS OGG_LIBRARY OGG_INCLUDE_DIR
|
||||
VERSION_VAR OGG_VERSION )
|
||||
|
||||
mark_as_advanced(OGG_INCLUDE_DIR OGG_LIBRARY)
|
56
cmakescripts/FindVorbis.cmake
Normal file
56
cmakescripts/FindVorbis.cmake
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
find_path(VORBIS_INCLUDE_DIR vorbis/codec.h
|
||||
HINTS
|
||||
ENV VORBIS_DIR
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(EXTRA_PATH_SUFFIX x64)
|
||||
else()
|
||||
set(EXTRA_PATH_SUFFIX x86)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (BUILD_STATIC)
|
||||
find_library(VORBIS_LIBRARY
|
||||
NAMES libvorbis.a vorbis vorbis-0 libvorbis-0
|
||||
HINTS
|
||||
ENV VORBIS_DIR
|
||||
PATH_SUFFIXES lib ${EXTRA_PATH_SUFFIX}
|
||||
)
|
||||
|
||||
find_library(VORBISFILE_LIBRARY
|
||||
NAMES libvorbisfile.a vorbisfile vorbisfile-3 libvorbisfile-3
|
||||
HINTS
|
||||
ENV VORBIS_DIR
|
||||
PATH_SUFFIXES lib ${EXTRA_PATH_SUFFIX}
|
||||
)
|
||||
else (BUILD_STATIC)
|
||||
find_library(VORBIS_LIBRARY
|
||||
NAMES vorbis vorbis-0 libvorbis-0
|
||||
HINTS
|
||||
ENV VORBIS_DIR
|
||||
PATH_SUFFIXES lib ${EXTRA_PATH_SUFFIX}
|
||||
)
|
||||
|
||||
find_library(VORBISFILE_LIBRARY
|
||||
NAMES vorbisfile vorbisfile-3 libvorbisfile-3
|
||||
HINTS
|
||||
ENV VORBIS_DIR
|
||||
PATH_SUFFIXES lib ${EXTRA_PATH_SUFFIX}
|
||||
)
|
||||
endif (BUILD_STATIC)
|
||||
|
||||
if(VORBIS_LIBRARY)
|
||||
set(VORBIS_VERSION 1)
|
||||
endif()
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set VORBIS_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(VORBIS REQUIRED_VARS VORBIS_LIBRARY VORBISFILE_LIBRARY VORBIS_INCLUDE_DIR
|
||||
VERSION_VAR VORBIS_VERSION )
|
||||
|
||||
mark_as_advanced(VORBIS_INCLUDE_DIR VORBIS_LIBRARY VORBISFILE_LIBRARY)
|
7
retroarch/ffmpeg.cfg
Normal file
7
retroarch/ffmpeg.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
vcodec = libx264
|
||||
acodec = libmp3lame
|
||||
pix_fmt = yuv444p
|
||||
video_preset = veryslow
|
||||
sample_rate = 44100
|
||||
video_qp = 0
|
||||
threads = 3
|
34
retroarch/libneocd_libretro.info
Normal file
34
retroarch/libneocd_libretro.info
Normal file
@ -0,0 +1,34 @@
|
||||
display_name = "SNK - Neo Geo CD (NeoCD)"
|
||||
authors = "Elta"
|
||||
supported_extensions = "cue"
|
||||
corename = "NeoCD"
|
||||
manufacturer = "SNK"
|
||||
categories = "Emulator"
|
||||
systemname = "SNK Neo Geo CD"
|
||||
database = "SNK - Neo Geo CD"
|
||||
license = "GPLv3"
|
||||
display_version = "2018"
|
||||
supports_no_game = "false"
|
||||
|
||||
firmware_count = 7
|
||||
firmware0_desc = "Front Loader BIOS"
|
||||
firmware0_path = "neocd/neocd_f.rom"
|
||||
firmware0_opt = "true"
|
||||
firmware1_desc = "Front Loader BIOS (SMKDAN)"
|
||||
firmware1_path = "neocd/neocd_sf.rom"
|
||||
firmware1_opt = "true"
|
||||
firmware2_desc = "Top Loader BIOS"
|
||||
firmware2_path = "neocd/neocd_t.rom"
|
||||
firmware2_opt = "true"
|
||||
firmware3_desc = "Top Loader BIOS (SMKDAN)"
|
||||
firmware3_path = "neocd/neocd_st.rom"
|
||||
firmware3_opt = "true"
|
||||
firmware4_desc = "CDZ BIOS"
|
||||
firmware4_path = "neocd/neocd_z.rom"
|
||||
firmware4_opt = "true"
|
||||
firmware5_desc = "CDZ BIOS (SMKDAN)"
|
||||
firmware5_path = "neocd/neocd_sz.rom"
|
||||
firmware5_opt = "true"
|
||||
firmware6_desc = "Y-ZOOM ROM"
|
||||
firmware6_path = "neocd/ng-lo.rom"
|
||||
firmware6_opt = "false"
|
358
src/3rdparty/musashi/m68k.h
vendored
Normal file
358
src/3rdparty/musashi/m68k.h
vendored
Normal file
@ -0,0 +1,358 @@
|
||||
/* ======================================================================== */
|
||||
/* ========================= LICENSING & COPYRIGHT ======================== */
|
||||
/* ======================================================================== */
|
||||
/*
|
||||
* MUSASHI
|
||||
* Version 3.4
|
||||
*
|
||||
* A portable Motorola M680x0 processor emulation engine.
|
||||
* Copyright 1998-2001 Karl Stenerud. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef M68K__HEADER
|
||||
#define M68K__HEADER
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================= CONFIGURATION ============================ */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* Import the configuration for this build */
|
||||
#include "m68kconf.h"
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================ GENERAL DEFINES =========================== */
|
||||
|
||||
/* ======================================================================== */
|
||||
|
||||
/* There are 7 levels of interrupt to the 68K.
|
||||
* A transition from < 7 to 7 will cause a non-maskable interrupt (NMI).
|
||||
*/
|
||||
#define M68K_IRQ_NONE 0
|
||||
#define M68K_IRQ_1 1
|
||||
#define M68K_IRQ_2 2
|
||||
#define M68K_IRQ_3 3
|
||||
#define M68K_IRQ_4 4
|
||||
#define M68K_IRQ_5 5
|
||||
#define M68K_IRQ_6 6
|
||||
#define M68K_IRQ_7 7
|
||||
|
||||
|
||||
/* Special interrupt acknowledge values.
|
||||
* Use these as special returns from the interrupt acknowledge callback
|
||||
* (specified later in this header).
|
||||
*/
|
||||
|
||||
/* Causes an interrupt autovector (0x18 + interrupt level) to be taken.
|
||||
* This happens in a real 68K if VPA or AVEC is asserted during an interrupt
|
||||
* acknowledge cycle instead of DTACK.
|
||||
*/
|
||||
#define M68K_INT_ACK_AUTOVECTOR 0xffffffff
|
||||
|
||||
/* Causes the spurious interrupt vector (0x18) to be taken
|
||||
* This happens in a real 68K if BERR is asserted during the interrupt
|
||||
* acknowledge cycle (i.e. no devices responded to the acknowledge).
|
||||
*/
|
||||
#define M68K_INT_ACK_SPURIOUS 0xfffffffe
|
||||
|
||||
|
||||
/* CPU types for use in m68k_set_cpu_type() */
|
||||
enum
|
||||
{
|
||||
M68K_CPU_TYPE_INVALID,
|
||||
M68K_CPU_TYPE_68000,
|
||||
M68K_CPU_TYPE_68010,
|
||||
M68K_CPU_TYPE_68EC020,
|
||||
M68K_CPU_TYPE_68020,
|
||||
M68K_CPU_TYPE_68030, /* Supported by disassembler ONLY */
|
||||
M68K_CPU_TYPE_68040 /* Supported by disassembler ONLY */
|
||||
};
|
||||
|
||||
/* Registers used by m68k_get_reg() and m68k_set_reg() */
|
||||
typedef enum
|
||||
{
|
||||
/* Real registers */
|
||||
M68K_REG_D0, /* Data registers */
|
||||
M68K_REG_D1,
|
||||
M68K_REG_D2,
|
||||
M68K_REG_D3,
|
||||
M68K_REG_D4,
|
||||
M68K_REG_D5,
|
||||
M68K_REG_D6,
|
||||
M68K_REG_D7,
|
||||
M68K_REG_A0, /* Address registers */
|
||||
M68K_REG_A1,
|
||||
M68K_REG_A2,
|
||||
M68K_REG_A3,
|
||||
M68K_REG_A4,
|
||||
M68K_REG_A5,
|
||||
M68K_REG_A6,
|
||||
M68K_REG_A7,
|
||||
M68K_REG_PC, /* Program Counter */
|
||||
M68K_REG_SR, /* Status Register */
|
||||
M68K_REG_SP, /* The current Stack Pointer (located in A7) */
|
||||
M68K_REG_USP, /* User Stack Pointer */
|
||||
M68K_REG_ISP, /* Interrupt Stack Pointer */
|
||||
M68K_REG_MSP, /* Master Stack Pointer */
|
||||
M68K_REG_SFC, /* Source Function Code */
|
||||
M68K_REG_DFC, /* Destination Function Code */
|
||||
M68K_REG_VBR, /* Vector Base Register */
|
||||
M68K_REG_CACR, /* Cache Control Register */
|
||||
M68K_REG_CAAR, /* Cache Address Register */
|
||||
|
||||
/* Assumed registers */
|
||||
/* These are cheat registers which emulate the 1-longword prefetch
|
||||
* present in the 68000 and 68010.
|
||||
*/
|
||||
M68K_REG_PREF_ADDR, /* Last prefetch address */
|
||||
M68K_REG_PREF_DATA, /* Last prefetch data */
|
||||
|
||||
/* Convenience registers */
|
||||
M68K_REG_PPC, /* Previous value in the program counter */
|
||||
M68K_REG_IR, /* Instruction register */
|
||||
M68K_REG_CPU_TYPE /* Type of CPU being run */
|
||||
} m68k_register_t;
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ====================== FUNCTIONS CALLED BY THE CPU ===================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* You will have to implement these functions */
|
||||
|
||||
/* read/write functions called by the CPU to access memory.
|
||||
* while values used are 32 bits, only the appropriate number
|
||||
* of bits are relevant (i.e. in write_memory_8, only the lower 8 bits
|
||||
* of value should be written to memory).
|
||||
*
|
||||
* NOTE: I have separated the immediate and PC-relative memory fetches
|
||||
* from the other memory fetches because some systems require
|
||||
* differentiation between PROGRAM and DATA fetches (usually
|
||||
* for security setups such as encryption).
|
||||
* This separation can either be achieved by setting
|
||||
* M68K_SEPARATE_READS in m68kconf.h and defining
|
||||
* the read functions, or by setting M68K_EMULATE_FC and
|
||||
* making a function code callback function.
|
||||
* Using the callback offers better emulation coverage
|
||||
* because you can also monitor whether the CPU is in SYSTEM or
|
||||
* USER mode, but it is also slower.
|
||||
*/
|
||||
|
||||
/* Read from anywhere */
|
||||
unsigned int m68k_read_memory_8(unsigned int address);
|
||||
unsigned int m68k_read_memory_16(unsigned int address);
|
||||
unsigned int m68k_read_memory_32(unsigned int address);
|
||||
|
||||
/* Read data immediately following the PC */
|
||||
unsigned int m68k_read_immediate_16(unsigned int address);
|
||||
unsigned int m68k_read_immediate_32(unsigned int address);
|
||||
|
||||
/* Read data relative to the PC */
|
||||
unsigned int m68k_read_pcrelative_8(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_16(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_32(unsigned int address);
|
||||
|
||||
/* Memory access for the disassembler */
|
||||
unsigned int m68k_read_disassembler_8 (unsigned int address);
|
||||
unsigned int m68k_read_disassembler_16 (unsigned int address);
|
||||
unsigned int m68k_read_disassembler_32 (unsigned int address);
|
||||
|
||||
/* Write to anywhere */
|
||||
void m68k_write_memory_8(unsigned int address, unsigned int value);
|
||||
void m68k_write_memory_16(unsigned int address, unsigned int value);
|
||||
void m68k_write_memory_32(unsigned int address, unsigned int value);
|
||||
|
||||
/* Special call to simulate undocumented 68k behavior when move.l with a
|
||||
* predecrement destination mode is executed.
|
||||
* To simulate real 68k behavior, first write the high word to
|
||||
* [address+2], and then write the low word to [address].
|
||||
*
|
||||
* Enable this functionality with M68K_SIMULATE_PD_WRITES in m68kconf.h.
|
||||
*/
|
||||
void m68k_write_memory_32_pd(unsigned int address, unsigned int value);
|
||||
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== CALLBACKS =============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* These functions allow you to set callbacks to the host when specific events
|
||||
* occur. Note that you must enable the corresponding value in m68kconf.h
|
||||
* in order for these to do anything useful.
|
||||
* Note: I have defined default callbacks which are used if you have enabled
|
||||
* the corresponding #define in m68kconf.h but either haven't assigned a
|
||||
* callback or have assigned a callback of NULL.
|
||||
*/
|
||||
|
||||
/* Set the callback for an interrupt acknowledge.
|
||||
* You must enable M68K_EMULATE_INT_ACK in m68kconf.h.
|
||||
* The CPU will call the callback with the interrupt level being acknowledged.
|
||||
* The host program must return either a vector from 0x02-0xff, or one of the
|
||||
* special interrupt acknowledge values specified earlier in this header.
|
||||
* If this is not implemented, the CPU will always assume an autovectored
|
||||
* interrupt, and will automatically clear the interrupt request when it
|
||||
* services the interrupt.
|
||||
* Default behavior: return M68K_INT_ACK_AUTOVECTOR.
|
||||
*/
|
||||
void m68k_set_int_ack_callback(int (*callback)(int int_level));
|
||||
|
||||
|
||||
/* Set the callback for a breakpoint acknowledge (68010+).
|
||||
* You must enable M68K_EMULATE_BKPT_ACK in m68kconf.h.
|
||||
* The CPU will call the callback with whatever was in the data field of the
|
||||
* BKPT instruction for 68020+, or 0 for 68010.
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_bkpt_ack_callback(void (*callback)(unsigned int data));
|
||||
|
||||
|
||||
/* Set the callback for the RESET instruction.
|
||||
* You must enable M68K_EMULATE_RESET in m68kconf.h.
|
||||
* The CPU calls this callback every time it encounters a RESET instruction.
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_reset_instr_callback(void (*callback)(void));
|
||||
|
||||
|
||||
/* Set the callback for informing of a large PC change.
|
||||
* You must enable M68K_MONITOR_PC in m68kconf.h.
|
||||
* The CPU calls this callback with the new PC value every time the PC changes
|
||||
* by a large value (currently set for changes by longwords).
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_pc_changed_callback(void (*callback)(unsigned int new_pc));
|
||||
|
||||
|
||||
/* Set the callback for CPU function code changes.
|
||||
* You must enable M68K_EMULATE_FC in m68kconf.h.
|
||||
* The CPU calls this callback with the function code before every memory
|
||||
* access to set the CPU's function code according to what kind of memory
|
||||
* access it is (supervisor/user, program/data and such).
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_fc_callback(void (*callback)(unsigned int new_fc));
|
||||
|
||||
|
||||
/* Set a callback for the instruction cycle of the CPU.
|
||||
* You must enable M68K_INSTRUCTION_HOOK in m68kconf.h.
|
||||
* The CPU calls this callback just before fetching the opcode in the
|
||||
* instruction cycle.
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_instr_hook_callback(void (*callback)(void));
|
||||
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ====================== FUNCTIONS TO ACCESS THE CPU ===================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* Use this function to set the CPU type you want to emulate.
|
||||
* Currently supported types are: M68K_CPU_TYPE_68000, M68K_CPU_TYPE_68010,
|
||||
* M68K_CPU_TYPE_EC020, and M68K_CPU_TYPE_68020.
|
||||
*/
|
||||
void m68k_set_cpu_type(unsigned int cpu_type);
|
||||
|
||||
/* Do whatever initialisations the core requires. Should be called
|
||||
* at least once at init time.
|
||||
*/
|
||||
void m68k_init(void);
|
||||
|
||||
/* Pulse the RESET pin on the CPU.
|
||||
* You *MUST* reset the CPU at least once to initialize the emulation
|
||||
* Note: If you didn't call m68k_set_cpu_type() before resetting
|
||||
* the CPU for the first time, the CPU will be set to
|
||||
* M68K_CPU_TYPE_68000.
|
||||
*/
|
||||
void m68k_pulse_reset(void);
|
||||
|
||||
/* execute num_cycles worth of instructions. returns number of cycles used */
|
||||
int m68k_execute(int num_cycles);
|
||||
|
||||
/* These functions let you read/write/modify the number of cycles left to run
|
||||
* while m68k_execute() is running.
|
||||
* These are useful if the 68k accesses a memory-mapped port on another device
|
||||
* that requires immediate processing by another CPU.
|
||||
*/
|
||||
int m68k_cycles_run(void); /* Number of cycles run so far */
|
||||
int m68k_cycles_remaining(void); /* Number of cycles left */
|
||||
void m68k_modify_timeslice(int cycles); /* Modify cycles left */
|
||||
void m68k_end_timeslice(void); /* End timeslice now */
|
||||
|
||||
/* Set the IPL0-IPL2 pins on the CPU (IRQ).
|
||||
* A transition from < 7 to 7 will cause a non-maskable interrupt (NMI).
|
||||
* Setting IRQ to 0 will clear an interrupt request.
|
||||
*/
|
||||
void m68k_set_irq(unsigned int int_level);
|
||||
|
||||
|
||||
/* Halt the CPU as if you pulsed the HALT pin. */
|
||||
void m68k_pulse_halt(void);
|
||||
|
||||
|
||||
/* Context switching to allow multiple CPUs */
|
||||
|
||||
/* Get the size of the cpu context in bytes */
|
||||
unsigned int m68k_context_size(void);
|
||||
|
||||
/* Get a cpu context */
|
||||
unsigned int m68k_get_context(void* dst);
|
||||
|
||||
/* set the current cpu context */
|
||||
void m68k_set_context(void* dst);
|
||||
|
||||
/* Register the CPU state information */
|
||||
void m68k_state_register(const char *type);
|
||||
|
||||
|
||||
/* Peek at the internals of a CPU context. This can either be a context
|
||||
* retrieved using m68k_get_context() or the currently running context.
|
||||
* If context is NULL, the currently running CPU context will be used.
|
||||
*/
|
||||
unsigned int m68k_get_reg(void* context, m68k_register_t reg);
|
||||
|
||||
/* Poke values into the internals of the currently running CPU context */
|
||||
void m68k_set_reg(m68k_register_t reg, unsigned int value);
|
||||
|
||||
/* Check if an instruction is valid for the specified CPU type */
|
||||
unsigned int m68k_is_valid_instruction(unsigned int instruction, unsigned int cpu_type);
|
||||
|
||||
/* Disassemble 1 instruction using the epecified CPU type at pc. Stores
|
||||
* disassembly in str_buff and returns the size of the instruction in bytes.
|
||||
*/
|
||||
unsigned int m68k_disassemble(char* str_buff, unsigned int pc, unsigned int cpu_type);
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== MAME STUFF ============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
#if M68K_COMPILE_FOR_MAME == OPT_ON
|
||||
#include "m68kmame.h"
|
||||
#endif /* M68K_COMPILE_FOR_MAME */
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== END OF FILE ============================= */
|
||||
/* ======================================================================== */
|
||||
|
||||
#endif /* M68K__HEADER */
|
200
src/3rdparty/musashi/m68kconf.h
vendored
Normal file
200
src/3rdparty/musashi/m68kconf.h
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
/* ======================================================================== */
|
||||
/* ========================= LICENSING & COPYRIGHT ======================== */
|
||||
/* ======================================================================== */
|
||||
/*
|
||||
* MUSASHI
|
||||
* Version 3.4
|
||||
*
|
||||
* A portable Motorola M680x0 processor emulation engine.
|
||||
* Copyright 1998-2001 Karl Stenerud. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef M68KCONF__HEADER
|
||||
#define M68KCONF__HEADER
|
||||
|
||||
|
||||
/* Configuration switches.
|
||||
* Use OPT_SPECIFY_HANDLER for configuration options that allow callbacks.
|
||||
* OPT_SPECIFY_HANDLER causes the core to link directly to the function
|
||||
* or macro you specify, rather than using callback functions whose pointer
|
||||
* must be passed in using m68k_set_xxx_callback().
|
||||
*/
|
||||
#define OPT_OFF 0
|
||||
#define OPT_ON 1
|
||||
#define OPT_SPECIFY_HANDLER 2
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== MAME STUFF ============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* If you're compiling this for MAME, only change M68K_COMPILE_FOR_MAME
|
||||
* to OPT_ON and use m68kmame.h to configure the 68k core.
|
||||
*/
|
||||
#ifndef M68K_COMPILE_FOR_MAME
|
||||
#define M68K_COMPILE_FOR_MAME OPT_OFF
|
||||
#endif /* M68K_COMPILE_FOR_MAME */
|
||||
|
||||
|
||||
#if M68K_COMPILE_FOR_MAME == OPT_OFF
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================= CONFIGURATION ============================ */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* Turn ON if you want to use the following M68K variants */
|
||||
#define M68K_EMULATE_010 OPT_OFF
|
||||
#define M68K_EMULATE_EC020 OPT_OFF
|
||||
#define M68K_EMULATE_020 OPT_OFF
|
||||
|
||||
|
||||
/* If ON, the CPU will call m68k_read_immediate_xx() for immediate addressing
|
||||
* and m68k_read_pcrelative_xx() for PC-relative addressing.
|
||||
* If off, all read requests from the CPU will be redirected to m68k_read_xx()
|
||||
*/
|
||||
#define M68K_SEPARATE_READS OPT_OFF
|
||||
|
||||
/* If ON, the CPU will call m68k_write_32_pd() when it executes move.l with a
|
||||
* predecrement destination EA mode instead of m68k_write_32().
|
||||
* To simulate real 68k behavior, m68k_write_32_pd() must first write the high
|
||||
* word to [address+2], and then write the low word to [address].
|
||||
*/
|
||||
#define M68K_SIMULATE_PD_WRITES OPT_OFF
|
||||
|
||||
/* If ON, CPU will call the interrupt acknowledge callback when it services an
|
||||
* interrupt.
|
||||
* If off, all interrupts will be autovectored and all interrupt requests will
|
||||
* auto-clear when the interrupt is serviced.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
int neocd_get_vector(int level);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#define M68K_EMULATE_INT_ACK OPT_SPECIFY_HANDLER
|
||||
#define M68K_INT_ACK_CALLBACK(A) neocd_get_vector(A)
|
||||
|
||||
|
||||
/* If ON, CPU will call the breakpoint acknowledge callback when it encounters
|
||||
* a breakpoint instruction and it is running a 68010+.
|
||||
*/
|
||||
#define M68K_EMULATE_BKPT_ACK OPT_OFF
|
||||
#define M68K_BKPT_ACK_CALLBACK() your_bkpt_ack_handler_function()
|
||||
|
||||
|
||||
/* If ON, the CPU will monitor the trace flags and take trace exceptions
|
||||
*/
|
||||
#define M68K_EMULATE_TRACE OPT_OFF
|
||||
|
||||
|
||||
/* If ON, CPU will call the output reset callback when it encounters a reset
|
||||
* instruction.
|
||||
*/
|
||||
#define M68K_EMULATE_RESET OPT_OFF
|
||||
#define M68K_RESET_CALLBACK() your_reset_handler_function()
|
||||
|
||||
|
||||
/* If ON, CPU will call the set fc callback on every memory access to
|
||||
* differentiate between user/supervisor, program/data access like a real
|
||||
* 68000 would. This should be enabled and the callback should be set if you
|
||||
* want to properly emulate the m68010 or higher. (moves uses function codes
|
||||
* to read/write data from different address spaces)
|
||||
*/
|
||||
#define M68K_EMULATE_FC OPT_OFF
|
||||
#define M68K_SET_FC_CALLBACK(A) your_set_fc_handler_function(A)
|
||||
|
||||
|
||||
/* If ON, CPU will call the pc changed callback when it changes the PC by a
|
||||
* large value. This allows host programs to be nicer when it comes to
|
||||
* fetching immediate data and instructions on a banked memory system.
|
||||
*/
|
||||
#define M68K_MONITOR_PC OPT_OFF
|
||||
#define M68K_SET_PC_CALLBACK(A) your_pc_changed_handler_function(A)
|
||||
|
||||
|
||||
/* If ON, CPU will call the instruction hook callback before every
|
||||
* instruction.
|
||||
*/
|
||||
#define M68K_INSTRUCTION_HOOK OPT_OFF
|
||||
#define M68K_INSTRUCTION_CALLBACK() your_instruction_hook_function()
|
||||
|
||||
|
||||
/* If ON, the CPU will emulate the 4-byte prefetch queue of a real 68000 */
|
||||
#define M68K_EMULATE_PREFETCH OPT_OFF
|
||||
|
||||
|
||||
/* If ON, the CPU will generate address error exceptions if it tries to
|
||||
* access a word or longword at an odd address.
|
||||
* NOTE: This is only emulated properly for 68000 mode.
|
||||
*/
|
||||
#define M68K_EMULATE_ADDRESS_ERROR OPT_ON
|
||||
|
||||
|
||||
/* Turn ON to enable logging of illegal instruction calls.
|
||||
* M68K_LOG_FILEHANDLE must be #defined to a stdio file stream.
|
||||
* Turn on M68K_LOG_1010_1111 to log all 1010 and 1111 calls.
|
||||
*/
|
||||
#define M68K_LOG_ENABLE OPT_OFF
|
||||
#define M68K_LOG_1010_1111 OPT_OFF
|
||||
#define M68K_LOG_FILEHANDLE some_file_handle
|
||||
|
||||
|
||||
/* ----------------------------- COMPATIBILITY ---------------------------- */
|
||||
|
||||
/* The following options set optimizations that violate the current ANSI
|
||||
* standard, but will be compliant under the forthcoming C9X standard.
|
||||
*/
|
||||
|
||||
|
||||
/* If ON, the enulation core will use 64-bit integers to speed up some
|
||||
* operations.
|
||||
*/
|
||||
#define M68K_USE_64_BIT OPT_OFF
|
||||
|
||||
|
||||
/* Set to your compiler's static inline keyword to enable it, or
|
||||
* set it to blank to disable it.
|
||||
* If you define INLINE in the makefile, it will override this value.
|
||||
* NOTE: not enabling inline functions will SEVERELY slow down emulation.
|
||||
*/
|
||||
#ifndef INLINE
|
||||
#ifdef _MSC_VER
|
||||
#define INLINE static __inline
|
||||
#else
|
||||
#define INLINE static __inline__
|
||||
#endif /* _MSC_VER */
|
||||
#endif /* INLINE */
|
||||
|
||||
#endif /* M68K_COMPILE_FOR_MAME */
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== END OF FILE ============================= */
|
||||
/* ======================================================================== */
|
||||
|
||||
#endif /* M68KCONF__HEADER */
|
884
src/3rdparty/musashi/m68kcpu.cpp
vendored
Normal file
884
src/3rdparty/musashi/m68kcpu.cpp
vendored
Normal file
@ -0,0 +1,884 @@
|
||||
/* ======================================================================== */
|
||||
/* ========================= LICENSING & COPYRIGHT ======================== */
|
||||
/* ======================================================================== */
|
||||
/*
|
||||
* MUSASHI
|
||||
* Version 3.4
|
||||
*
|
||||
* A portable Motorola M680x0 processor emulation engine.
|
||||
* Copyright 1998-2001 Karl Stenerud. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ================================= NOTES ================================ */
|
||||
/* ======================================================================== */
|
||||
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ================================ INCLUDES ============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
#include "m68kops.h"
|
||||
#include "m68kcpu.h"
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ================================= DATA ================================= */
|
||||
/* ======================================================================== */
|
||||
|
||||
int m68ki_initial_cycles;
|
||||
int m68ki_remaining_cycles = 0; /* Number of clocks remaining */
|
||||
uint m68ki_tracing = 0;
|
||||
uint m68ki_address_space;
|
||||
|
||||
#ifdef M68K_LOG_ENABLE
|
||||
const char* m68ki_cpu_names[9] =
|
||||
{
|
||||
"Invalid CPU",
|
||||
"M68000",
|
||||
"M68010",
|
||||
"Invalid CPU",
|
||||
"M68EC020"
|
||||
"Invalid CPU",
|
||||
"Invalid CPU",
|
||||
"Invalid CPU",
|
||||
"M68020"
|
||||
};
|
||||
#endif /* M68K_LOG_ENABLE */
|
||||
|
||||
/* The CPU core */
|
||||
m68ki_cpu_core m68ki_cpu = {0};
|
||||
|
||||
/*
|
||||
#if M68K_EMULATE_ADDRESS_ERROR
|
||||
jmp_buf m68ki_aerr_trap;
|
||||
#endif
|
||||
*/
|
||||
|
||||
uint m68ki_aerr_address;
|
||||
uint m68ki_aerr_write_mode;
|
||||
uint m68ki_aerr_fc;
|
||||
|
||||
/* Used by shift & rotate instructions */
|
||||
uint8 m68ki_shift_8_table[65] =
|
||||
{
|
||||
0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
uint16 m68ki_shift_16_table[65] =
|
||||
{
|
||||
0x0000, 0x8000, 0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00, 0xff00,
|
||||
0xff80, 0xffc0, 0xffe0, 0xfff0, 0xfff8, 0xfffc, 0xfffe, 0xffff, 0xffff,
|
||||
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
|
||||
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
|
||||
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
|
||||
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
|
||||
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
|
||||
0xffff, 0xffff
|
||||
};
|
||||
uint m68ki_shift_32_table[65] =
|
||||
{
|
||||
0x00000000, 0x80000000, 0xc0000000, 0xe0000000, 0xf0000000, 0xf8000000,
|
||||
0xfc000000, 0xfe000000, 0xff000000, 0xff800000, 0xffc00000, 0xffe00000,
|
||||
0xfff00000, 0xfff80000, 0xfffc0000, 0xfffe0000, 0xffff0000, 0xffff8000,
|
||||
0xffffc000, 0xffffe000, 0xfffff000, 0xfffff800, 0xfffffc00, 0xfffffe00,
|
||||
0xffffff00, 0xffffff80, 0xffffffc0, 0xffffffe0, 0xfffffff0, 0xfffffff8,
|
||||
0xfffffffc, 0xfffffffe, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
||||
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
||||
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
||||
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
||||
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
||||
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
|
||||
};
|
||||
|
||||
|
||||
/* Number of clock cycles to use for exception processing.
|
||||
* I used 4 for any vectors that are undocumented for processing times.
|
||||
*/
|
||||
uint8 m68ki_exception_cycle_table[3][256] =
|
||||
{
|
||||
{ /* 000 */
|
||||
4, /* 0: Reset - Initial Stack Pointer */
|
||||
4, /* 1: Reset - Initial Program Counter */
|
||||
50, /* 2: Bus Error (unemulated) */
|
||||
50, /* 3: Address Error (unemulated) */
|
||||
34, /* 4: Illegal Instruction */
|
||||
38, /* 5: Divide by Zero -- ASG: changed from 42 */
|
||||
40, /* 6: CHK -- ASG: chanaged from 44 */
|
||||
34, /* 7: TRAPV */
|
||||
34, /* 8: Privilege Violation */
|
||||
34, /* 9: Trace */
|
||||
34, /* 10: 1010 */
|
||||
34, /* 11: 1111 */
|
||||
4, /* 12: RESERVED */
|
||||
4, /* 13: Coprocessor Protocol Violation (unemulated) */
|
||||
4, /* 14: Format Error */
|
||||
44, /* 15: Uninitialized Interrupt */
|
||||
4, /* 16: RESERVED */
|
||||
4, /* 17: RESERVED */
|
||||
4, /* 18: RESERVED */
|
||||
4, /* 19: RESERVED */
|
||||
4, /* 20: RESERVED */
|
||||
4, /* 21: RESERVED */
|
||||
4, /* 22: RESERVED */
|
||||
4, /* 23: RESERVED */
|
||||
44, /* 24: Spurious Interrupt */
|
||||
44, /* 25: Level 1 Interrupt Autovector */
|
||||
44, /* 26: Level 2 Interrupt Autovector */
|
||||
44, /* 27: Level 3 Interrupt Autovector */
|
||||
44, /* 28: Level 4 Interrupt Autovector */
|
||||
44, /* 29: Level 5 Interrupt Autovector */
|
||||
44, /* 30: Level 6 Interrupt Autovector */
|
||||
44, /* 31: Level 7 Interrupt Autovector */
|
||||
34, /* 32: TRAP #0 -- ASG: chanaged from 38 */
|
||||
34, /* 33: TRAP #1 */
|
||||
34, /* 34: TRAP #2 */
|
||||
34, /* 35: TRAP #3 */
|
||||
34, /* 36: TRAP #4 */
|
||||
34, /* 37: TRAP #5 */
|
||||
34, /* 38: TRAP #6 */
|
||||
34, /* 39: TRAP #7 */
|
||||
34, /* 40: TRAP #8 */
|
||||
34, /* 41: TRAP #9 */
|
||||
34, /* 42: TRAP #10 */
|
||||
34, /* 43: TRAP #11 */
|
||||
34, /* 44: TRAP #12 */
|
||||
34, /* 45: TRAP #13 */
|
||||
34, /* 46: TRAP #14 */
|
||||
34, /* 47: TRAP #15 */
|
||||
4, /* 48: FP Branch or Set on Unknown Condition (unemulated) */
|
||||
4, /* 49: FP Inexact Result (unemulated) */
|
||||
4, /* 50: FP Divide by Zero (unemulated) */
|
||||
4, /* 51: FP Underflow (unemulated) */
|
||||
4, /* 52: FP Operand Error (unemulated) */
|
||||
4, /* 53: FP Overflow (unemulated) */
|
||||
4, /* 54: FP Signaling NAN (unemulated) */
|
||||
4, /* 55: FP Unimplemented Data Type (unemulated) */
|
||||
4, /* 56: MMU Configuration Error (unemulated) */
|
||||
4, /* 57: MMU Illegal Operation Error (unemulated) */
|
||||
4, /* 58: MMU Access Level Violation Error (unemulated) */
|
||||
4, /* 59: RESERVED */
|
||||
4, /* 60: RESERVED */
|
||||
4, /* 61: RESERVED */
|
||||
4, /* 62: RESERVED */
|
||||
4, /* 63: RESERVED */
|
||||
/* 64-255: User Defined */
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4
|
||||
},
|
||||
{ /* 010 */
|
||||
4, /* 0: Reset - Initial Stack Pointer */
|
||||
4, /* 1: Reset - Initial Program Counter */
|
||||
126, /* 2: Bus Error (unemulated) */
|
||||
126, /* 3: Address Error (unemulated) */
|
||||
38, /* 4: Illegal Instruction */
|
||||
44, /* 5: Divide by Zero */
|
||||
44, /* 6: CHK */
|
||||
34, /* 7: TRAPV */
|
||||
38, /* 8: Privilege Violation */
|
||||
38, /* 9: Trace */
|
||||
4, /* 10: 1010 */
|
||||
4, /* 11: 1111 */
|
||||
4, /* 12: RESERVED */
|
||||
4, /* 13: Coprocessor Protocol Violation (unemulated) */
|
||||
4, /* 14: Format Error */
|
||||
44, /* 15: Uninitialized Interrupt */
|
||||
4, /* 16: RESERVED */
|
||||
4, /* 17: RESERVED */
|
||||
4, /* 18: RESERVED */
|
||||
4, /* 19: RESERVED */
|
||||
4, /* 20: RESERVED */
|
||||
4, /* 21: RESERVED */
|
||||
4, /* 22: RESERVED */
|
||||
4, /* 23: RESERVED */
|
||||
46, /* 24: Spurious Interrupt */
|
||||
46, /* 25: Level 1 Interrupt Autovector */
|
||||
46, /* 26: Level 2 Interrupt Autovector */
|
||||
46, /* 27: Level 3 Interrupt Autovector */
|
||||
46, /* 28: Level 4 Interrupt Autovector */
|
||||
46, /* 29: Level 5 Interrupt Autovector */
|
||||
46, /* 30: Level 6 Interrupt Autovector */
|
||||
46, /* 31: Level 7 Interrupt Autovector */
|
||||
38, /* 32: TRAP #0 */
|
||||
38, /* 33: TRAP #1 */
|
||||
38, /* 34: TRAP #2 */
|
||||
38, /* 35: TRAP #3 */
|
||||
38, /* 36: TRAP #4 */
|
||||
38, /* 37: TRAP #5 */
|
||||
38, /* 38: TRAP #6 */
|
||||
38, /* 39: TRAP #7 */
|
||||
38, /* 40: TRAP #8 */
|
||||
38, /* 41: TRAP #9 */
|
||||
38, /* 42: TRAP #10 */
|
||||
38, /* 43: TRAP #11 */
|
||||
38, /* 44: TRAP #12 */
|
||||
38, /* 45: TRAP #13 */
|
||||
38, /* 46: TRAP #14 */
|
||||
38, /* 47: TRAP #15 */
|
||||
4, /* 48: FP Branch or Set on Unknown Condition (unemulated) */
|
||||
4, /* 49: FP Inexact Result (unemulated) */
|
||||
4, /* 50: FP Divide by Zero (unemulated) */
|
||||
4, /* 51: FP Underflow (unemulated) */
|
||||
4, /* 52: FP Operand Error (unemulated) */
|
||||
4, /* 53: FP Overflow (unemulated) */
|
||||
4, /* 54: FP Signaling NAN (unemulated) */
|
||||
4, /* 55: FP Unimplemented Data Type (unemulated) */
|
||||
4, /* 56: MMU Configuration Error (unemulated) */
|
||||
4, /* 57: MMU Illegal Operation Error (unemulated) */
|
||||
4, /* 58: MMU Access Level Violation Error (unemulated) */
|
||||
4, /* 59: RESERVED */
|
||||
4, /* 60: RESERVED */
|
||||
4, /* 61: RESERVED */
|
||||
4, /* 62: RESERVED */
|
||||
4, /* 63: RESERVED */
|
||||
/* 64-255: User Defined */
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4
|
||||
},
|
||||
{ /* 020 */
|
||||
4, /* 0: Reset - Initial Stack Pointer */
|
||||
4, /* 1: Reset - Initial Program Counter */
|
||||
50, /* 2: Bus Error (unemulated) */
|
||||
50, /* 3: Address Error (unemulated) */
|
||||
20, /* 4: Illegal Instruction */
|
||||
38, /* 5: Divide by Zero */
|
||||
40, /* 6: CHK */
|
||||
20, /* 7: TRAPV */
|
||||
34, /* 8: Privilege Violation */
|
||||
25, /* 9: Trace */
|
||||
20, /* 10: 1010 */
|
||||
20, /* 11: 1111 */
|
||||
4, /* 12: RESERVED */
|
||||
4, /* 13: Coprocessor Protocol Violation (unemulated) */
|
||||
4, /* 14: Format Error */
|
||||
30, /* 15: Uninitialized Interrupt */
|
||||
4, /* 16: RESERVED */
|
||||
4, /* 17: RESERVED */
|
||||
4, /* 18: RESERVED */
|
||||
4, /* 19: RESERVED */
|
||||
4, /* 20: RESERVED */
|
||||
4, /* 21: RESERVED */
|
||||
4, /* 22: RESERVED */
|
||||
4, /* 23: RESERVED */
|
||||
30, /* 24: Spurious Interrupt */
|
||||
30, /* 25: Level 1 Interrupt Autovector */
|
||||
30, /* 26: Level 2 Interrupt Autovector */
|
||||
30, /* 27: Level 3 Interrupt Autovector */
|
||||
30, /* 28: Level 4 Interrupt Autovector */
|
||||
30, /* 29: Level 5 Interrupt Autovector */
|
||||
30, /* 30: Level 6 Interrupt Autovector */
|
||||
30, /* 31: Level 7 Interrupt Autovector */
|
||||
20, /* 32: TRAP #0 */
|
||||
20, /* 33: TRAP #1 */
|
||||
20, /* 34: TRAP #2 */
|
||||
20, /* 35: TRAP #3 */
|
||||
20, /* 36: TRAP #4 */
|
||||
20, /* 37: TRAP #5 */
|
||||
20, /* 38: TRAP #6 */
|
||||
20, /* 39: TRAP #7 */
|
||||
20, /* 40: TRAP #8 */
|
||||
20, /* 41: TRAP #9 */
|
||||
20, /* 42: TRAP #10 */
|
||||
20, /* 43: TRAP #11 */
|
||||
20, /* 44: TRAP #12 */
|
||||
20, /* 45: TRAP #13 */
|
||||
20, /* 46: TRAP #14 */
|
||||
20, /* 47: TRAP #15 */
|
||||
4, /* 48: FP Branch or Set on Unknown Condition (unemulated) */
|
||||
4, /* 49: FP Inexact Result (unemulated) */
|
||||
4, /* 50: FP Divide by Zero (unemulated) */
|
||||
4, /* 51: FP Underflow (unemulated) */
|
||||
4, /* 52: FP Operand Error (unemulated) */
|
||||
4, /* 53: FP Overflow (unemulated) */
|
||||
4, /* 54: FP Signaling NAN (unemulated) */
|
||||
4, /* 55: FP Unimplemented Data Type (unemulated) */
|
||||
4, /* 56: MMU Configuration Error (unemulated) */
|
||||
4, /* 57: MMU Illegal Operation Error (unemulated) */
|
||||
4, /* 58: MMU Access Level Violation Error (unemulated) */
|
||||
4, /* 59: RESERVED */
|
||||
4, /* 60: RESERVED */
|
||||
4, /* 61: RESERVED */
|
||||
4, /* 62: RESERVED */
|
||||
4, /* 63: RESERVED */
|
||||
/* 64-255: User Defined */
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
||||
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4
|
||||
}
|
||||
};
|
||||
|
||||
uint8 m68ki_ea_idx_cycle_table[64] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, /* ..01.000 no memory indirect, base NULL */
|
||||
5, /* ..01..01 memory indirect, base NULL, outer NULL */
|
||||
7, /* ..01..10 memory indirect, base NULL, outer 16 */
|
||||
7, /* ..01..11 memory indirect, base NULL, outer 32 */
|
||||
0, 5, 7, 7, 0, 5, 7, 7, 0, 5, 7, 7,
|
||||
2, /* ..10.000 no memory indirect, base 16 */
|
||||
7, /* ..10..01 memory indirect, base 16, outer NULL */
|
||||
9, /* ..10..10 memory indirect, base 16, outer 16 */
|
||||
9, /* ..10..11 memory indirect, base 16, outer 32 */
|
||||
0, 7, 9, 9, 0, 7, 9, 9, 0, 7, 9, 9,
|
||||
6, /* ..11.000 no memory indirect, base 32 */
|
||||
11, /* ..11..01 memory indirect, base 32, outer NULL */
|
||||
13, /* ..11..10 memory indirect, base 32, outer 16 */
|
||||
13, /* ..11..11 memory indirect, base 32, outer 32 */
|
||||
0, 11, 13, 13, 0, 11, 13, 13, 0, 11, 13, 13
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* =============================== CALLBACKS ============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* Default callbacks used if the callback hasn't been set yet, or if the
|
||||
* callback is set to NULL
|
||||
*/
|
||||
|
||||
/* Interrupt acknowledge */
|
||||
static int default_int_ack_callback_data;
|
||||
static int default_int_ack_callback(int int_level)
|
||||
{
|
||||
default_int_ack_callback_data = int_level;
|
||||
CPU_INT_LEVEL = 0;
|
||||
return M68K_INT_ACK_AUTOVECTOR;
|
||||
}
|
||||
|
||||
/* Breakpoint acknowledge */
|
||||
static unsigned int default_bkpt_ack_callback_data;
|
||||
static void default_bkpt_ack_callback(unsigned int data)
|
||||
{
|
||||
default_bkpt_ack_callback_data = data;
|
||||
}
|
||||
|
||||
/* Called when a reset instruction is executed */
|
||||
static void default_reset_instr_callback(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* Called when the program counter changed by a large value */
|
||||
static unsigned int default_pc_changed_callback_data;
|
||||
static void default_pc_changed_callback(unsigned int new_pc)
|
||||
{
|
||||
default_pc_changed_callback_data = new_pc;
|
||||
}
|
||||
|
||||
/* Called every time there's bus activity (read/write to/from memory */
|
||||
static unsigned int default_set_fc_callback_data;
|
||||
static void default_set_fc_callback(unsigned int new_fc)
|
||||
{
|
||||
default_set_fc_callback_data = new_fc;
|
||||
}
|
||||
|
||||
/* Called every instruction cycle prior to execution */
|
||||
static void default_instr_hook_callback(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#if M68K_EMULATE_ADDRESS_ERROR
|
||||
#include <setjmp.h>
|
||||
jmp_buf m68ki_aerr_trap;
|
||||
#endif /* M68K_EMULATE_ADDRESS_ERROR */
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ================================= API ================================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* Access the internals of the CPU */
|
||||
unsigned int m68k_get_reg(void* context, m68k_register_t regnum)
|
||||
{
|
||||
m68ki_cpu_core* cpu = context != NULL ?(m68ki_cpu_core*)context : &m68ki_cpu;
|
||||
|
||||
switch(regnum)
|
||||
{
|
||||
case M68K_REG_D0: return cpu->dar[0];
|
||||
case M68K_REG_D1: return cpu->dar[1];
|
||||
case M68K_REG_D2: return cpu->dar[2];
|
||||
case M68K_REG_D3: return cpu->dar[3];
|
||||
case M68K_REG_D4: return cpu->dar[4];
|
||||
case M68K_REG_D5: return cpu->dar[5];
|
||||
case M68K_REG_D6: return cpu->dar[6];
|
||||
case M68K_REG_D7: return cpu->dar[7];
|
||||
case M68K_REG_A0: return cpu->dar[8];
|
||||
case M68K_REG_A1: return cpu->dar[9];
|
||||
case M68K_REG_A2: return cpu->dar[10];
|
||||
case M68K_REG_A3: return cpu->dar[11];
|
||||
case M68K_REG_A4: return cpu->dar[12];
|
||||
case M68K_REG_A5: return cpu->dar[13];
|
||||
case M68K_REG_A6: return cpu->dar[14];
|
||||
case M68K_REG_A7: return cpu->dar[15];
|
||||
case M68K_REG_PC: return MASK_OUT_ABOVE_32(cpu->pc);
|
||||
case M68K_REG_SR: return cpu->t1_flag |
|
||||
cpu->t0_flag |
|
||||
(cpu->s_flag << 11) |
|
||||
(cpu->m_flag << 11) |
|
||||
cpu->int_mask |
|
||||
((cpu->x_flag & XFLAG_SET) >> 4) |
|
||||
((cpu->n_flag & NFLAG_SET) >> 4) |
|
||||
((!cpu->not_z_flag) << 2) |
|
||||
((cpu->v_flag & VFLAG_SET) >> 6) |
|
||||
((cpu->c_flag & CFLAG_SET) >> 8);
|
||||
case M68K_REG_SP: return cpu->dar[15];
|
||||
case M68K_REG_USP: return cpu->s_flag ? cpu->sp[0] : cpu->dar[15];
|
||||
case M68K_REG_ISP: return cpu->s_flag && !cpu->m_flag ? cpu->dar[15] : cpu->sp[4];
|
||||
case M68K_REG_MSP: return cpu->s_flag && cpu->m_flag ? cpu->dar[15] : cpu->sp[6];
|
||||
case M68K_REG_SFC: return cpu->sfc;
|
||||
case M68K_REG_DFC: return cpu->dfc;
|
||||
case M68K_REG_VBR: return cpu->vbr;
|
||||
case M68K_REG_CACR: return cpu->cacr;
|
||||
case M68K_REG_CAAR: return cpu->caar;
|
||||
case M68K_REG_PREF_ADDR: return cpu->pref_addr;
|
||||
case M68K_REG_PREF_DATA: return cpu->pref_data;
|
||||
case M68K_REG_PPC: return MASK_OUT_ABOVE_32(cpu->ppc);
|
||||
case M68K_REG_IR: return cpu->ir;
|
||||
case M68K_REG_CPU_TYPE:
|
||||
switch(cpu->cpu_type)
|
||||
{
|
||||
case CPU_TYPE_000: return (unsigned int)M68K_CPU_TYPE_68000;
|
||||
case CPU_TYPE_010: return (unsigned int)M68K_CPU_TYPE_68010;
|
||||
case CPU_TYPE_EC020: return (unsigned int)M68K_CPU_TYPE_68EC020;
|
||||
case CPU_TYPE_020: return (unsigned int)M68K_CPU_TYPE_68020;
|
||||
}
|
||||
return M68K_CPU_TYPE_INVALID;
|
||||
default: return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void m68k_set_reg(m68k_register_t regnum, unsigned int value)
|
||||
{
|
||||
switch(regnum)
|
||||
{
|
||||
case M68K_REG_D0: REG_D[0] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_D1: REG_D[1] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_D2: REG_D[2] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_D3: REG_D[3] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_D4: REG_D[4] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_D5: REG_D[5] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_D6: REG_D[6] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_D7: REG_D[7] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_A0: REG_A[0] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_A1: REG_A[1] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_A2: REG_A[2] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_A3: REG_A[3] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_A4: REG_A[4] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_A5: REG_A[5] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_A6: REG_A[6] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_A7: REG_A[7] = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_PC: m68ki_jump(MASK_OUT_ABOVE_32(value)); return;
|
||||
case M68K_REG_SR: m68ki_set_sr(value); return;
|
||||
case M68K_REG_SP: REG_SP = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_USP: if(FLAG_S)
|
||||
REG_USP = MASK_OUT_ABOVE_32(value);
|
||||
else
|
||||
REG_SP = MASK_OUT_ABOVE_32(value);
|
||||
return;
|
||||
case M68K_REG_ISP: if(FLAG_S && !FLAG_M)
|
||||
REG_SP = MASK_OUT_ABOVE_32(value);
|
||||
else
|
||||
REG_ISP = MASK_OUT_ABOVE_32(value);
|
||||
return;
|
||||
case M68K_REG_MSP: if(FLAG_S && FLAG_M)
|
||||
REG_SP = MASK_OUT_ABOVE_32(value);
|
||||
else
|
||||
REG_MSP = MASK_OUT_ABOVE_32(value);
|
||||
return;
|
||||
case M68K_REG_VBR: REG_VBR = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_SFC: REG_SFC = value & 7; return;
|
||||
case M68K_REG_DFC: REG_DFC = value & 7; return;
|
||||
case M68K_REG_CACR: REG_CACR = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_CAAR: REG_CAAR = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_PPC: REG_PPC = MASK_OUT_ABOVE_32(value); return;
|
||||
case M68K_REG_IR: REG_IR = MASK_OUT_ABOVE_16(value); return;
|
||||
case M68K_REG_CPU_TYPE: m68k_set_cpu_type(value); return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the callbacks */
|
||||
void m68k_set_int_ack_callback(int (*callback)(int int_level))
|
||||
{
|
||||
CALLBACK_INT_ACK = callback ? callback : default_int_ack_callback;
|
||||
}
|
||||
|
||||
void m68k_set_bkpt_ack_callback(void (*callback)(unsigned int data))
|
||||
{
|
||||
CALLBACK_BKPT_ACK = callback ? callback : default_bkpt_ack_callback;
|
||||
}
|
||||
|
||||
void m68k_set_reset_instr_callback(void (*callback)(void))
|
||||
{
|
||||
CALLBACK_RESET_INSTR = callback ? callback : default_reset_instr_callback;
|
||||
}
|
||||
|
||||
void m68k_set_pc_changed_callback(void (*callback)(unsigned int new_pc))
|
||||
{
|
||||
CALLBACK_PC_CHANGED = callback ? callback : default_pc_changed_callback;
|
||||
}
|
||||
|
||||
void m68k_set_fc_callback(void (*callback)(unsigned int new_fc))
|
||||
{
|
||||
CALLBACK_SET_FC = callback ? callback : default_set_fc_callback;
|
||||
}
|
||||
|
||||
void m68k_set_instr_hook_callback(void (*callback)(void))
|
||||
{
|
||||
CALLBACK_INSTR_HOOK = callback ? callback : default_instr_hook_callback;
|
||||
}
|
||||
|
||||
#include <stdio.h>
|
||||
/* Set the CPU type. */
|
||||
void m68k_set_cpu_type(unsigned int cpu_type)
|
||||
{
|
||||
switch(cpu_type)
|
||||
{
|
||||
case M68K_CPU_TYPE_68000:
|
||||
CPU_TYPE = CPU_TYPE_000;
|
||||
CPU_ADDRESS_MASK = 0x00ffffff;
|
||||
CPU_SR_MASK = 0xa71f; /* T1 -- S -- -- I2 I1 I0 -- -- -- X N Z V C */
|
||||
CYC_INSTRUCTION = m68ki_cycles[0];
|
||||
CYC_EXCEPTION = m68ki_exception_cycle_table[0];
|
||||
CYC_BCC_NOTAKE_B = -2;
|
||||
CYC_BCC_NOTAKE_W = 2;
|
||||
CYC_DBCC_F_NOEXP = -2;
|
||||
CYC_DBCC_F_EXP = 2;
|
||||
CYC_SCC_R_TRUE = 2;
|
||||
CYC_MOVEM_W = 2;
|
||||
CYC_MOVEM_L = 3;
|
||||
CYC_SHIFT = 1;
|
||||
CYC_RESET = 132;
|
||||
return;
|
||||
case M68K_CPU_TYPE_68010:
|
||||
CPU_TYPE = CPU_TYPE_010;
|
||||
CPU_ADDRESS_MASK = 0x00ffffff;
|
||||
CPU_SR_MASK = 0xa71f; /* T1 -- S -- -- I2 I1 I0 -- -- -- X N Z V C */
|
||||
CYC_INSTRUCTION = m68ki_cycles[1];
|
||||
CYC_EXCEPTION = m68ki_exception_cycle_table[1];
|
||||
CYC_BCC_NOTAKE_B = -4;
|
||||
CYC_BCC_NOTAKE_W = 0;
|
||||
CYC_DBCC_F_NOEXP = 0;
|
||||
CYC_DBCC_F_EXP = 6;
|
||||
CYC_SCC_R_TRUE = 0;
|
||||
CYC_MOVEM_W = 2;
|
||||
CYC_MOVEM_L = 3;
|
||||
CYC_SHIFT = 1;
|
||||
CYC_RESET = 130;
|
||||
return;
|
||||
case M68K_CPU_TYPE_68EC020:
|
||||
CPU_TYPE = CPU_TYPE_EC020;
|
||||
CPU_ADDRESS_MASK = 0x00ffffff;
|
||||
CPU_SR_MASK = 0xf71f; /* T1 T0 S M -- I2 I1 I0 -- -- -- X N Z V C */
|
||||
CYC_INSTRUCTION = m68ki_cycles[2];
|
||||
CYC_EXCEPTION = m68ki_exception_cycle_table[2];
|
||||
CYC_BCC_NOTAKE_B = -2;
|
||||
CYC_BCC_NOTAKE_W = 0;
|
||||
CYC_DBCC_F_NOEXP = 0;
|
||||
CYC_DBCC_F_EXP = 4;
|
||||
CYC_SCC_R_TRUE = 0;
|
||||
CYC_MOVEM_W = 2;
|
||||
CYC_MOVEM_L = 2;
|
||||
CYC_SHIFT = 0;
|
||||
CYC_RESET = 518;
|
||||
return;
|
||||
case M68K_CPU_TYPE_68020:
|
||||
CPU_TYPE = CPU_TYPE_020;
|
||||
CPU_ADDRESS_MASK = 0xffffffff;
|
||||
CPU_SR_MASK = 0xf71f; /* T1 T0 S M -- I2 I1 I0 -- -- -- X N Z V C */
|
||||
CYC_INSTRUCTION = m68ki_cycles[2];
|
||||
CYC_EXCEPTION = m68ki_exception_cycle_table[2];
|
||||
CYC_BCC_NOTAKE_B = -2;
|
||||
CYC_BCC_NOTAKE_W = 0;
|
||||
CYC_DBCC_F_NOEXP = 0;
|
||||
CYC_DBCC_F_EXP = 4;
|
||||
CYC_SCC_R_TRUE = 0;
|
||||
CYC_MOVEM_W = 2;
|
||||
CYC_MOVEM_L = 2;
|
||||
CYC_SHIFT = 0;
|
||||
CYC_RESET = 518;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute some instructions until we use up num_cycles clock cycles */
|
||||
/* ASG: removed per-instruction interrupt checks */
|
||||
int m68k_execute(int num_cycles)
|
||||
{
|
||||
/* Make sure we're not stopped */
|
||||
if(!CPU_STOPPED)
|
||||
{
|
||||
/* Set our pool of clock cycles available */
|
||||
SET_CYCLES(num_cycles);
|
||||
m68ki_initial_cycles = num_cycles;
|
||||
|
||||
/* ASG: update cycles */
|
||||
USE_CYCLES(CPU_INT_CYCLES);
|
||||
CPU_INT_CYCLES = 0;
|
||||
|
||||
/* Return point if we had an address error */
|
||||
m68ki_set_address_error_trap(); /* auto-disable (see m68kcpu.h) */
|
||||
|
||||
/* Main loop. Keep going until we run out of clock cycles */
|
||||
do
|
||||
{
|
||||
/* Set tracing accodring to T1. (T0 is done inside instruction) */
|
||||
m68ki_trace_t1(); /* auto-disable (see m68kcpu.h) */
|
||||
|
||||
/* Set the address space for reads */
|
||||
m68ki_use_data_space(); /* auto-disable (see m68kcpu.h) */
|
||||
|
||||
/* Call external hook to peek at CPU */
|
||||
m68ki_instr_hook(); /* auto-disable (see m68kcpu.h) */
|
||||
|
||||
/* Record previous program counter */
|
||||
REG_PPC = REG_PC;
|
||||
|
||||
/* Read an instruction and call its handler */
|
||||
REG_IR = m68ki_read_imm_16();
|
||||
m68ki_instruction_jump_table[REG_IR]();
|
||||
USE_CYCLES(CYC_INSTRUCTION[REG_IR]);
|
||||
|
||||
/* Trace m68k_exception, if necessary */
|
||||
m68ki_exception_if_trace(); /* auto-disable (see m68kcpu.h) */
|
||||
} while(GET_CYCLES() > 0);
|
||||
|
||||
/* set previous PC to current PC for the next entry into the loop */
|
||||
REG_PPC = REG_PC;
|
||||
|
||||
/* ASG: update cycles */
|
||||
USE_CYCLES(CPU_INT_CYCLES);
|
||||
CPU_INT_CYCLES = 0;
|
||||
|
||||
/* return how many clocks we used */
|
||||
return m68ki_initial_cycles - GET_CYCLES();
|
||||
}
|
||||
|
||||
/* We get here if the CPU is stopped or halted */
|
||||
SET_CYCLES(0);
|
||||
CPU_INT_CYCLES = 0;
|
||||
|
||||
return num_cycles;
|
||||
}
|
||||
|
||||
|
||||
int m68k_cycles_run(void)
|
||||
{
|
||||
return m68ki_initial_cycles - GET_CYCLES();
|
||||
}
|
||||
|
||||
int m68k_cycles_remaining(void)
|
||||
{
|
||||
return GET_CYCLES();
|
||||
}
|
||||
|
||||
/* Change the timeslice */
|
||||
void m68k_modify_timeslice(int cycles)
|
||||
{
|
||||
m68ki_initial_cycles += cycles;
|
||||
ADD_CYCLES(cycles);
|
||||
}
|
||||
|
||||
|
||||
void m68k_end_timeslice(void)
|
||||
{
|
||||
m68ki_initial_cycles = GET_CYCLES();
|
||||
SET_CYCLES(0);
|
||||
}
|
||||
|
||||
|
||||
/* ASG: rewrote so that the int_level is a mask of the IPL0/IPL1/IPL2 bits */
|
||||
/* KS: Modified so that IPL* bits match with mask positions in the SR
|
||||
* and cleaned out remenants of the interrupt controller.
|
||||
*/
|
||||
void m68k_set_irq(unsigned int int_level)
|
||||
{
|
||||
uint old_level = CPU_INT_LEVEL;
|
||||
CPU_INT_LEVEL = int_level << 8;
|
||||
|
||||
/* A transition from < 7 to 7 always interrupts (NMI) */
|
||||
/* Note: Level 7 can also level trigger like a normal IRQ */
|
||||
if(old_level != 0x0700 && CPU_INT_LEVEL == 0x0700)
|
||||
m68ki_exception_interrupt(7); /* Edge triggered level 7 (NMI) */
|
||||
else
|
||||
m68ki_check_interrupts(); /* Level triggered (IRQ) */
|
||||
}
|
||||
|
||||
void m68k_init(void)
|
||||
{
|
||||
static uint emulation_initialized = 0;
|
||||
|
||||
/* The first call to this function initializes the opcode handler jump table */
|
||||
if(!emulation_initialized)
|
||||
{
|
||||
m68ki_build_opcode_table();
|
||||
emulation_initialized = 1;
|
||||
}
|
||||
|
||||
m68k_set_int_ack_callback(NULL);
|
||||
m68k_set_bkpt_ack_callback(NULL);
|
||||
m68k_set_reset_instr_callback(NULL);
|
||||
m68k_set_pc_changed_callback(NULL);
|
||||
m68k_set_fc_callback(NULL);
|
||||
m68k_set_instr_hook_callback(NULL);
|
||||
}
|
||||
|
||||
/* Pulse the RESET line on the CPU */
|
||||
void m68k_pulse_reset(void)
|
||||
{
|
||||
/* Clear all stop levels and eat up all remaining cycles */
|
||||
CPU_STOPPED = 0;
|
||||
SET_CYCLES(0);
|
||||
|
||||
CPU_RUN_MODE = RUN_MODE_BERR_AERR_RESET;
|
||||
CPU_INSTR_MODE = INSTRUCTION_YES;
|
||||
|
||||
/* Turn off tracing */
|
||||
FLAG_T1 = FLAG_T0 = 0;
|
||||
m68ki_clear_trace();
|
||||
/* Interrupt mask to level 7 */
|
||||
FLAG_INT_MASK = 0x0700;
|
||||
/* Reset VBR */
|
||||
REG_VBR = 0;
|
||||
/* Go to supervisor mode */
|
||||
m68ki_set_sm_flag(SFLAG_SET | MFLAG_CLEAR);
|
||||
|
||||
/* Invalidate the prefetch queue */
|
||||
#if M68K_EMULATE_PREFETCH
|
||||
/* Set to arbitrary number since our first fetch is from 0 */
|
||||
CPU_PREF_ADDR = 0x1000;
|
||||
#endif /* M68K_EMULATE_PREFETCH */
|
||||
|
||||
/* Read the initial stack pointer and program counter */
|
||||
m68ki_jump(0);
|
||||
REG_SP = m68ki_read_imm_32();
|
||||
REG_PC = m68ki_read_imm_32();
|
||||
m68ki_jump(REG_PC);
|
||||
|
||||
CPU_RUN_MODE = RUN_MODE_NORMAL;
|
||||
}
|
||||
|
||||
/* Pulse the HALT line on the CPU */
|
||||
void m68k_pulse_halt(void)
|
||||
{
|
||||
CPU_STOPPED |= STOP_LEVEL_HALT;
|
||||
}
|
||||
|
||||
|
||||
/* Get and set the current CPU context */
|
||||
/* This is to allow for multiple CPUs */
|
||||
unsigned int m68k_context_size()
|
||||
{
|
||||
return sizeof(m68ki_cpu_core);
|
||||
}
|
||||
|
||||
unsigned int m68k_get_context(void* dst)
|
||||
{
|
||||
if(dst) *(m68ki_cpu_core*)dst = m68ki_cpu;
|
||||
return sizeof(m68ki_cpu_core);
|
||||
}
|
||||
|
||||
void m68k_set_context(void* src)
|
||||
{
|
||||
if(src) m68ki_cpu = *(m68ki_cpu_core*)src;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== MAME STUFF ============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
#if M68K_COMPILE_FOR_MAME == OPT_ON
|
||||
|
||||
#include "state.h"
|
||||
|
||||
static struct {
|
||||
UINT16 sr;
|
||||
int stopped;
|
||||
int halted;
|
||||
} m68k_substate;
|
||||
|
||||
static void m68k_prepare_substate(void)
|
||||
{
|
||||
m68k_substate.sr = m68ki_get_sr();
|
||||
m68k_substate.stopped = (CPU_STOPPED & STOP_LEVEL_STOP) != 0;
|
||||
m68k_substate.halted = (CPU_STOPPED & STOP_LEVEL_HALT) != 0;
|
||||
}
|
||||
|
||||
static void m68k_post_load(void)
|
||||
{
|
||||
m68ki_set_sr_noint_nosp(m68k_substate.sr);
|
||||
CPU_STOPPED = m68k_substate.stopped ? STOP_LEVEL_STOP : 0
|
||||
| m68k_substate.halted ? STOP_LEVEL_HALT : 0;
|
||||
m68ki_jump(REG_PC);
|
||||
}
|
||||
|
||||
void m68k_state_register(const char *type)
|
||||
{
|
||||
int cpu = cpu_getactivecpu();
|
||||
|
||||
state_save_register_UINT32(type, cpu, "D" , REG_D, 8);
|
||||
state_save_register_UINT32(type, cpu, "A" , REG_A, 8);
|
||||
state_save_register_UINT32(type, cpu, "PPC" , ®_PPC, 1);
|
||||
state_save_register_UINT32(type, cpu, "PC" , ®_PC, 1);
|
||||
state_save_register_UINT32(type, cpu, "USP" , ®_USP, 1);
|
||||
state_save_register_UINT32(type, cpu, "ISP" , ®_ISP, 1);
|
||||
state_save_register_UINT32(type, cpu, "MSP" , ®_MSP, 1);
|
||||
state_save_register_UINT32(type, cpu, "VBR" , ®_VBR, 1);
|
||||
state_save_register_UINT32(type, cpu, "SFC" , ®_SFC, 1);
|
||||
state_save_register_UINT32(type, cpu, "DFC" , ®_DFC, 1);
|
||||
state_save_register_UINT32(type, cpu, "CACR" , ®_CACR, 1);
|
||||
state_save_register_UINT32(type, cpu, "CAAR" , ®_CAAR, 1);
|
||||
state_save_register_UINT16(type, cpu, "SR" , &m68k_substate.sr, 1);
|
||||
state_save_register_UINT32(type, cpu, "INT_LEVEL" , &CPU_INT_LEVEL, 1);
|
||||
state_save_register_UINT32(type, cpu, "INT_CYCLES", &CPU_INT_CYCLES, 1);
|
||||
state_save_register_int (type, cpu, "STOPPED" , &m68k_substate.stopped);
|
||||
state_save_register_int (type, cpu, "HALTED" , &m68k_substate.halted);
|
||||
state_save_register_UINT32(type, cpu, "PREF_ADDR" , &CPU_PREF_ADDR, 1);
|
||||
state_save_register_UINT32(type, cpu, "PREF_DATA" , &CPU_PREF_DATA, 1);
|
||||
state_save_register_func_presave(m68k_prepare_substate);
|
||||
state_save_register_func_postload(m68k_post_load);
|
||||
}
|
||||
|
||||
#endif /* M68K_COMPILE_FOR_MAME */
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== END OF FILE ============================= */
|
||||
/* ======================================================================== */
|
1994
src/3rdparty/musashi/m68kcpu.h
vendored
Normal file
1994
src/3rdparty/musashi/m68kcpu.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12066
src/3rdparty/musashi/m68kopac.cpp
vendored
Normal file
12066
src/3rdparty/musashi/m68kopac.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
13223
src/3rdparty/musashi/m68kopdm.cpp
vendored
Normal file
13223
src/3rdparty/musashi/m68kopdm.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
8817
src/3rdparty/musashi/m68kopnz.cpp
vendored
Normal file
8817
src/3rdparty/musashi/m68kopnz.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2104
src/3rdparty/musashi/m68kops.cpp
vendored
Normal file
2104
src/3rdparty/musashi/m68kops.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1986
src/3rdparty/musashi/m68kops.h
vendored
Normal file
1986
src/3rdparty/musashi/m68kops.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3542
src/3rdparty/ym/ym2610.cpp
vendored
Normal file
3542
src/3rdparty/ym/ym2610.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
58
src/3rdparty/ym/ym2610.h
vendored
Normal file
58
src/3rdparty/ym/ym2610.h
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/***************************************************************************
|
||||
* NeoCD - The Neo Geo CD emulator *
|
||||
* Copyright (C) 2008 by Fabrice Martinez *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef _YM2610_H_
|
||||
#define _YM2610_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class DataPacker;
|
||||
|
||||
/*
|
||||
for busy flag emulation , function FM_GET_TIME_NOW() should be
|
||||
return the present time in second unit with (double) value
|
||||
in timer.c
|
||||
*/
|
||||
#define FM_GET_TIME_NOW() neocd.currentTimeSeconds
|
||||
|
||||
typedef int16_t FMSAMPLE;
|
||||
typedef int32_t FMSAMPLE_MIX;
|
||||
|
||||
typedef void(*FM_TIMERHANDLER) (int channel, int count, double stepTime);
|
||||
typedef void(*FM_IRQHANDLER) (int irq);
|
||||
|
||||
int YM2610Init(int baseclock, int rate,
|
||||
void *pcmroma, int pcmsizea,
|
||||
#if YM2610_ADPCMB
|
||||
void *pcmromb, int pcmsizeb,
|
||||
#endif
|
||||
FM_TIMERHANDLER TimerHandler,
|
||||
FM_IRQHANDLER IRQHandler);
|
||||
|
||||
void YM2610Reset(void);
|
||||
void YM2610Update(int length);
|
||||
int YM2610Write(int addr, uint8_t value);
|
||||
uint8_t YM2610Read(int addr);
|
||||
int YM2610TimerOver(int channel);
|
||||
|
||||
bool YM2610SaveState(DataPacker& out);
|
||||
bool YM2610RestoreState(DataPacker& in);
|
||||
|
||||
#endif /* _YM2610_H_ */
|
3806
src/3rdparty/z80/z80.cpp
vendored
Normal file
3806
src/3rdparty/z80/z80.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
74
src/3rdparty/z80/z80.h
vendored
Normal file
74
src/3rdparty/z80/z80.h
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
#ifndef _Z80_H_
|
||||
#define _Z80_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "../../endian.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Interrupt line constants */
|
||||
enum
|
||||
{
|
||||
/* line states */
|
||||
CLEAR_LINE = 0, /* clear (a fired, held or pulsed) line */
|
||||
ASSERT_LINE, /* assert an interrupt immediately */
|
||||
HOLD_LINE, /* hold interrupt line until acknowledged */
|
||||
PULSE_LINE, /* pulse interrupt line for one instruction */
|
||||
INPUT_LINE_NMI,
|
||||
};
|
||||
|
||||
#define Uint8 uint8_t
|
||||
#define Sint8 int8_t
|
||||
#define Uint16 uint16_t
|
||||
#define Uint32 uint32_t
|
||||
|
||||
typedef union
|
||||
{
|
||||
#ifdef LITTLE_ENDIAN
|
||||
struct { Uint8 l, h, h2, h3; } b;
|
||||
struct { Uint16 l, h; } w;
|
||||
#else
|
||||
struct { Uint8 h3, h2, h, l; } b;
|
||||
struct { Uint16 h, l; } w;
|
||||
#endif
|
||||
Uint32 d;
|
||||
} PAIR;
|
||||
|
||||
/****************************************************************************/
|
||||
/* The Z80 registers. HALT is set to 1 when the CPU is halted, the refresh */
|
||||
/* register is calculated as follows: refresh=(Z80.r&127)|(Z80.r2&128) */
|
||||
/****************************************************************************/
|
||||
typedef struct
|
||||
{
|
||||
PAIR prvpc, pc, sp, af, bc, de, hl, ix, iy;
|
||||
PAIR af2, bc2, de2, hl2;
|
||||
Uint8 r, r2, iff1, iff2, halt, im, i;
|
||||
Uint8 nmi_state; /* nmi line state */
|
||||
Uint8 nmi_pending; /* nmi pending */
|
||||
Uint8 irq_state; /* irq line state */
|
||||
Uint8 after_ei; /* are we in the EI shadow? */
|
||||
const struct z80_irq_daisy_chain *daisy;
|
||||
int(*irq_callback)(int irqline);
|
||||
} Z80_Regs;
|
||||
|
||||
extern Z80_Regs Z80;
|
||||
|
||||
void z80_init ( int index, int clock, const void *config, int ( *irqcallback ) ( int ) );
|
||||
void z80_reset ( void );
|
||||
void z80_exit ( void );
|
||||
int z80_execute ( int cycles );
|
||||
void z80_set_irq_line ( int irqline, int state );
|
||||
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
unsigned z80_dasm ( char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram );
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
76
src/3rdparty/z80/z80daisy.cpp
vendored
Normal file
76
src/3rdparty/z80/z80daisy.cpp
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/***************************************************************************
|
||||
|
||||
z80daisy.c
|
||||
|
||||
Z80/180 daisy chaining support functions.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#include "z80.h"
|
||||
#include "z80daisy.h"
|
||||
|
||||
#define logerror(...)
|
||||
|
||||
void z80daisy_reset(const struct z80_irq_daisy_chain *daisy)
|
||||
{
|
||||
/* loop over all devices and call their reset function */
|
||||
for ( ; daisy->param != -1; daisy++)
|
||||
if (daisy->reset)
|
||||
(*daisy->reset)(daisy->param);
|
||||
}
|
||||
|
||||
|
||||
int z80daisy_update_irq_state(const struct z80_irq_daisy_chain *daisy)
|
||||
{
|
||||
/* loop over all devices; dev[0] is highest priority */
|
||||
for ( ; daisy->param != -1; daisy++)
|
||||
{
|
||||
int state = (*daisy->irq_state)(daisy->param);
|
||||
|
||||
/* if this device is asserting the INT line, that's the one we want */
|
||||
if (state & Z80_DAISY_INT)
|
||||
return ASSERT_LINE;
|
||||
|
||||
/* if this device is asserting the IEO line, it blocks everyone else */
|
||||
if (state & Z80_DAISY_IEO)
|
||||
return CLEAR_LINE;
|
||||
}
|
||||
|
||||
return CLEAR_LINE;
|
||||
}
|
||||
|
||||
|
||||
int z80daisy_call_ack_device(const struct z80_irq_daisy_chain *daisy)
|
||||
{
|
||||
/* loop over all devices; dev[0] is the highest priority */
|
||||
for ( ; daisy->param != -1; daisy++)
|
||||
{
|
||||
int state = (*daisy->irq_state)(daisy->param);
|
||||
|
||||
/* if this device is asserting the INT line, that's the one we want */
|
||||
if (state & Z80_DAISY_INT)
|
||||
return (*daisy->irq_ack)(daisy->param);
|
||||
}
|
||||
|
||||
logerror("z80daisy_call_ack_device: failed to find an device to ack!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void z80daisy_call_reti_device(const struct z80_irq_daisy_chain *daisy)
|
||||
{
|
||||
/* loop over all devices; dev[0] is the highest priority */
|
||||
for ( ; daisy->param != -1; daisy++)
|
||||
{
|
||||
int state = (*daisy->irq_state)(daisy->param);
|
||||
|
||||
/* if this device is asserting the IEO line, that's the one we want */
|
||||
if (state & Z80_DAISY_IEO)
|
||||
{
|
||||
(*daisy->irq_reti)(daisy->param);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logerror("z80daisy_call_reti_device: failed to find an device to reti!\n");
|
||||
}
|
36
src/3rdparty/z80/z80daisy.h
vendored
Normal file
36
src/3rdparty/z80/z80daisy.h
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/***************************************************************************
|
||||
|
||||
z80daisy.h
|
||||
|
||||
Z80/180 daisy chaining support functions.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef Z80DAISY_H
|
||||
#define Z80DAISY_H
|
||||
|
||||
|
||||
/* daisy-chain link */
|
||||
struct z80_irq_daisy_chain
|
||||
{
|
||||
void (*reset)(int); /* reset callback */
|
||||
int (*irq_state)(int); /* get interrupt state */
|
||||
int (*irq_ack)(int); /* interrupt acknowledge callback */
|
||||
void (*irq_reti)(int); /* reti callback */
|
||||
int param; /* callback parameter (-1 ends list) */
|
||||
};
|
||||
|
||||
|
||||
/* these constants are returned from the irq_state function */
|
||||
#define Z80_DAISY_INT 0x01 /* interrupt request mask */
|
||||
#define Z80_DAISY_IEO 0x02 /* interrupt disable mask (IEO) */
|
||||
|
||||
|
||||
/* prototypes */
|
||||
void z80daisy_reset(const struct z80_irq_daisy_chain *daisy);
|
||||
int z80daisy_update_irq_state(const struct z80_irq_daisy_chain *chain);
|
||||
int z80daisy_call_ack_device(const struct z80_irq_daisy_chain *chain);
|
||||
void z80daisy_call_reti_device(const struct z80_irq_daisy_chain *chain);
|
||||
|
||||
#endif
|
10
src/always_inline.h
Normal file
10
src/always_inline.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef ALWAYS_INLINE_H
|
||||
#define ALWAYS_INLINE_H
|
||||
|
||||
#if __has_attribute(always_inline)
|
||||
#define ALWAYS_INLINE __attribute__((always_inline))
|
||||
#else
|
||||
#define ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
#endif // ALWAYS_INLINE_H
|
127
src/audio.cpp
Normal file
127
src/audio.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
#include "3rdparty/ym/ym2610.h"
|
||||
#include "3rdparty/z80/z80.h"
|
||||
#include "audio.h"
|
||||
#include "neogeocd.h"
|
||||
#include "timer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
// Replace with the std version once c++17 is finally widely available
|
||||
template <typename T>
|
||||
constexpr const T& clamp(const T& val, const T& min, const T& max)
|
||||
{
|
||||
return std::max(min, std::min(max, val));
|
||||
}
|
||||
|
||||
void YM2610UpdateRequest(void)
|
||||
{
|
||||
if (neocd.audio.currentSample > neocd.audio.audioWritePointer + 4)
|
||||
YM2610Update(neocd.audio.currentSample - neocd.audio.audioWritePointer);
|
||||
}
|
||||
|
||||
void YM2610TimerHandler(int channel, int count, double steptime)
|
||||
{
|
||||
double time_seconds;
|
||||
uint32_t time_cycles;
|
||||
|
||||
if (!count)
|
||||
{
|
||||
if (!channel)
|
||||
neocd.timers.ym2610TimerA->setState(Timer::Stopped);
|
||||
else
|
||||
neocd.timers.ym2610TimerB->setState(Timer::Stopped);
|
||||
}
|
||||
else
|
||||
{
|
||||
time_seconds = (double)count * steptime;
|
||||
time_cycles = (uint32_t)Timer::secondsToMaster(time_seconds);
|
||||
|
||||
if (!channel)
|
||||
neocd.timers.ym2610TimerA->arm(time_cycles);
|
||||
else
|
||||
neocd.timers.ym2610TimerB->arm(time_cycles);
|
||||
}
|
||||
}
|
||||
|
||||
void YM2610IrqHandler(int irq)
|
||||
{
|
||||
if (irq)
|
||||
z80_set_irq_line(0, ASSERT_LINE);
|
||||
else
|
||||
z80_set_irq_line(0, CLEAR_LINE);
|
||||
}
|
||||
|
||||
Audio::Audio() :
|
||||
hasCdAudioThisFrame(false),
|
||||
samplesThisFrameF(0.0),
|
||||
samplesThisFrame(0),
|
||||
currentSample(0),
|
||||
audioWritePointer(0)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void Audio::reset()
|
||||
{
|
||||
std::memset(cdAudioBuffer, 0, sizeof(cdAudioBuffer));
|
||||
std::memset(audioBuffer, 0, sizeof(audioBuffer));
|
||||
samplesThisFrameF = 0.0;
|
||||
samplesThisFrame = 0;
|
||||
currentSample = 0;
|
||||
hasCdAudioThisFrame = false;
|
||||
audioWritePointer = 0;
|
||||
}
|
||||
|
||||
void Audio::initFrame()
|
||||
{
|
||||
samplesThisFrameF += (static_cast<double>(SAMPLE_RATE) / Timer::FRAME_RATE);
|
||||
samplesThisFrame = static_cast<int>(std::ceil(samplesThisFrameF));
|
||||
samplesThisFrameF -= samplesThisFrame;
|
||||
audioWritePointer = 0;
|
||||
|
||||
if (neocd.cdrom.isPlaying() && neocd.cdrom.isAudio())
|
||||
{
|
||||
hasCdAudioThisFrame = true;
|
||||
neocd.cdrom.readAudio(reinterpret_cast<char*>(cdAudioBuffer), samplesThisFrame * 4);
|
||||
}
|
||||
else
|
||||
hasCdAudioThisFrame = false;
|
||||
}
|
||||
|
||||
void Audio::updateCurrentSample()
|
||||
{
|
||||
currentSample = static_cast<uint32_t>(std::round(static_cast<double>(Timer::CYCLES_PER_FRAME - std::max(0, neocd.remainingCyclesThisFrame)) * (samplesThisFrame - 1) / Timer::CYCLES_PER_FRAME));
|
||||
}
|
||||
|
||||
void Audio::finalize()
|
||||
{
|
||||
// Generate YM2610 samples
|
||||
if (audioWritePointer < samplesThisFrame)
|
||||
YM2610Update(samplesThisFrame - audioWritePointer);
|
||||
|
||||
// If we have audio CD samples...
|
||||
if (!hasCdAudioThisFrame)
|
||||
return;
|
||||
|
||||
// ...mix the two audio buffers
|
||||
std::transform(audioBuffer, audioBuffer + (samplesThisFrame * 2), cdAudioBuffer, audioBuffer, [](int16_t sampleA, int16_t sampleB) {
|
||||
return static_cast<int16_t>(clamp(
|
||||
static_cast<int32_t>(sampleA) + static_cast<int32_t>(sampleB),
|
||||
static_cast<int32_t>(std::numeric_limits<int16_t>::min()),
|
||||
static_cast<int32_t>(std::numeric_limits<int16_t>::max())));
|
||||
});
|
||||
}
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const Audio& audio)
|
||||
{
|
||||
out << audio.samplesThisFrameF;
|
||||
return out;
|
||||
}
|
||||
|
||||
DataPacker& operator>>(DataPacker& in, Audio& audio)
|
||||
{
|
||||
in >> audio.samplesThisFrameF;
|
||||
return in;
|
||||
}
|
93
src/audio.h
Normal file
93
src/audio.h
Normal file
@ -0,0 +1,93 @@
|
||||
#ifndef AUDIO_H
|
||||
#define AUDIO_H
|
||||
|
||||
#include "timer.h"
|
||||
#include "3rdparty/ym/ym2610.h"
|
||||
#include "datapacker.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
extern void YM2610UpdateRequest(void);
|
||||
extern void YM2610TimerHandler(int channel, int count, double steptime);
|
||||
extern void YM2610IrqHandler(int irq);
|
||||
|
||||
/**
|
||||
* @class Audio
|
||||
* @brief The audio class generates audio samples that are then passed to RetroArch
|
||||
*/
|
||||
class Audio
|
||||
{
|
||||
public:
|
||||
/// The sample rate of generated audio
|
||||
static constexpr uint32_t SAMPLE_RATE = 44100;
|
||||
|
||||
/// The audio buffer size, enough for one frame of audio.
|
||||
static constexpr uint32_t BUFFER_SIZE = static_cast<uint32_t>(static_cast<double>(SAMPLE_RATE * Timer::SCREEN_WIDTH) / (Timer::PIXEL_CLOCK / static_cast<double>(Timer::SCREEN_HEIGHT)) + 1.0);
|
||||
|
||||
Audio();
|
||||
|
||||
/// Non copyable
|
||||
Audio(const Audio&) = delete;
|
||||
|
||||
/// Non copyable
|
||||
void operator=(const Audio&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Reset all members to a known state
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief Start generating a frame of audio.
|
||||
* This will get CD audio data from the CDROM class.
|
||||
* CD audio is loaded and decoded in a separate thread.
|
||||
*/
|
||||
void initFrame();
|
||||
|
||||
/**
|
||||
* @brief Calculate the sample number corresponding to the current execution time.
|
||||
*/
|
||||
void updateCurrentSample();
|
||||
|
||||
/**
|
||||
* @brief Generate YM2610 samples for this frame and mix with CD audio if needed
|
||||
*/
|
||||
void finalize();
|
||||
|
||||
/// This bool is true if there is cd audio to mix for this frame
|
||||
bool hasCdAudioThisFrame;
|
||||
|
||||
/// Buffer for the generated audio
|
||||
int16_t cdAudioBuffer[BUFFER_SIZE * 2];
|
||||
|
||||
/// Buffer for the generated audio
|
||||
int16_t audioBuffer[BUFFER_SIZE * 2];
|
||||
|
||||
/// How many samples to generate this frame (this can vary because of rounding)
|
||||
double samplesThisFrameF;
|
||||
|
||||
/// How many samples to generate this frame (this can vary because of rounding)
|
||||
uint32_t samplesThisFrame;
|
||||
|
||||
/// The index of the sample corresponding to the current emulated time
|
||||
uint32_t currentSample;
|
||||
|
||||
/// Write index for audio data
|
||||
uint32_t audioWritePointer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Save Audio state to a DataPacker
|
||||
* @param out DataPacker to save to
|
||||
* @param audio The audio state
|
||||
*/
|
||||
DataPacker& operator<<(DataPacker& out, const Audio& audio);
|
||||
|
||||
/**
|
||||
* @brief Restore Audio state from a DataPacker
|
||||
* @param in DataPacker to read state from
|
||||
* @param audio The audio state
|
||||
*/
|
||||
DataPacker& operator>>(DataPacker& in, Audio& audio);
|
||||
|
||||
#endif // AUDIO_H
|
745
src/cdrom.cpp
Normal file
745
src/cdrom.cpp
Normal file
@ -0,0 +1,745 @@
|
||||
#include <regex>
|
||||
|
||||
#include "cdrom.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
class cueSourceFile
|
||||
{
|
||||
public:
|
||||
cueSourceFile(uint32_t ss, uint32_t sc) : sectorSize(ss), sectorCount(sc), entries()
|
||||
{}
|
||||
|
||||
uint32_t sectorSize;
|
||||
uint32_t sectorCount;
|
||||
std::map<TrackIndex, uint32_t> entries;
|
||||
};
|
||||
|
||||
#if defined(WIN32) || defined(WIN64)
|
||||
#define strcasecmp _stricmp
|
||||
#endif
|
||||
|
||||
static bool stringCompareInsensitive(const std::string& a, const std::string& b)
|
||||
{
|
||||
if (a.length() != b.length())
|
||||
return false;
|
||||
|
||||
return (strcasecmp(a.c_str(), b.c_str()) == 0);
|
||||
}
|
||||
|
||||
static std::string pathReplaceFilename(const std::string& path, const std::string& newFilename)
|
||||
{
|
||||
static const std::regex pathWithoutFilenameRegex("^(.*[\\\\/]).+$");
|
||||
|
||||
std::smatch matchResults;
|
||||
std::regex_match(path, matchResults, pathWithoutFilenameRegex);
|
||||
if (matchResults.size() < 2)
|
||||
return newFilename;
|
||||
|
||||
return matchResults[1].str() + newFilename;
|
||||
}
|
||||
|
||||
static bool fileIsFlac(const std::string& path)
|
||||
{
|
||||
static const std::regex fileIsFlacRegex("^.*\\.flac$", std::regex_constants::icase);
|
||||
return std::regex_match(path, fileIsFlacRegex);
|
||||
}
|
||||
|
||||
static bool fileIsOgg(const std::string& path)
|
||||
{
|
||||
static const std::regex fileIsOggRegex("^.*\\.ogg$", std::regex_constants::icase);
|
||||
return std::regex_match(path, fileIsOggRegex);
|
||||
}
|
||||
|
||||
static bool fileIsIso(const std::string& path)
|
||||
{
|
||||
static const std::regex fileIsIsoRegex("^.*\\.iso$", std::regex_constants::icase);
|
||||
return std::regex_match(path, fileIsIsoRegex);
|
||||
}
|
||||
|
||||
static bool fileIsImage(const std::string& path)
|
||||
{
|
||||
static const std::regex fileIsImageRegex("^.*\\.(?:bin|img)$", std::regex_constants::icase);
|
||||
return std::regex_match(path, fileIsImageRegex);
|
||||
}
|
||||
|
||||
static bool fileIsWav(const std::string& path)
|
||||
{
|
||||
static const std::regex fileIsWavRegex("^.*\\.wav$", std::regex_constants::icase);
|
||||
return std::regex_match(path, fileIsWavRegex);
|
||||
}
|
||||
|
||||
Cdrom::Cdrom() :
|
||||
m_currentPosition(0),
|
||||
m_isPlaying(false),
|
||||
m_currentTrack(nullptr),
|
||||
m_circularBuffer(),
|
||||
m_audioWorkerThreadCreated(false),
|
||||
m_exitFlag(false),
|
||||
m_workerThread(),
|
||||
m_workerMutex(),
|
||||
m_workerConditionVariable(),
|
||||
m_file(),
|
||||
m_flacFile(),
|
||||
m_oggFile(),
|
||||
m_wavFile(),
|
||||
m_leadout(0),
|
||||
m_toc(),
|
||||
m_tocByTrack(),
|
||||
m_tocByLBA()
|
||||
{
|
||||
initialize();
|
||||
m_circularBuffer.set_capacity(1048576);
|
||||
}
|
||||
|
||||
Cdrom::~Cdrom()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void Cdrom::createWorkerThread()
|
||||
{
|
||||
m_exitFlag = false;
|
||||
|
||||
if (!m_audioWorkerThreadCreated)
|
||||
{
|
||||
m_audioWorkerThreadCreated = true;
|
||||
m_workerThread = std::thread(&Cdrom::audioBufferWorker, this);
|
||||
}
|
||||
}
|
||||
|
||||
void Cdrom::endWorkerThread()
|
||||
{
|
||||
m_exitFlag = true;
|
||||
|
||||
if (m_audioWorkerThreadCreated)
|
||||
{
|
||||
m_workerConditionVariable.notify_all();
|
||||
if (m_workerThread.joinable())
|
||||
m_workerThread.join();
|
||||
|
||||
m_audioWorkerThreadCreated = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Cdrom::initialize()
|
||||
{
|
||||
cleanup();
|
||||
|
||||
m_isPlaying = false;
|
||||
m_currentPosition = 0;
|
||||
m_currentTrack = nullptr;
|
||||
m_leadout = 0;
|
||||
m_toc.clear();
|
||||
m_tocByTrack.clear();
|
||||
m_tocByLBA.clear();
|
||||
}
|
||||
|
||||
void Cdrom::reset()
|
||||
{
|
||||
m_isPlaying = false;
|
||||
m_currentPosition = 0;
|
||||
m_currentTrack = nullptr;
|
||||
seek(0);
|
||||
}
|
||||
|
||||
bool Cdrom::findFileLength(const std::string& filename, size_t& length, uint32_t& sectorSize)
|
||||
{
|
||||
std::ifstream in;
|
||||
in.open(filename, std::ios::in | std::ios::binary);
|
||||
if (!in.is_open())
|
||||
{
|
||||
LOG(LOG_ERROR, "Could not open file: %s\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
sectorSize = 2352;
|
||||
|
||||
if (fileIsIso(filename))
|
||||
{
|
||||
in.seekg(0, std::ios::end);
|
||||
length = static_cast<size_t>(in.tellg());
|
||||
sectorSize = 2048;
|
||||
}
|
||||
else if (fileIsImage(filename))
|
||||
{
|
||||
in.seekg(0, std::ios::end);
|
||||
length = static_cast<size_t>(in.tellg());
|
||||
}
|
||||
else if (fileIsWav(filename))
|
||||
{
|
||||
if (!m_wavFile.initialize(&in))
|
||||
{
|
||||
LOG(LOG_ERROR, "Could not open WAVE file: %s\nFile was not found or had incorrect format.\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
length = m_wavFile.length();
|
||||
m_wavFile.cleanup();
|
||||
}
|
||||
else if (fileIsFlac(filename))
|
||||
{
|
||||
if (!m_flacFile.initialize(&in))
|
||||
{
|
||||
LOG(LOG_ERROR, "Could not open FLAC file: %s\nFile was not found or had incorrect format.\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
length = m_flacFile.length();
|
||||
m_flacFile.cleanup();
|
||||
}
|
||||
else if (fileIsOgg(filename))
|
||||
{
|
||||
if (!m_oggFile.initialize(&in))
|
||||
{
|
||||
LOG(LOG_ERROR, "Could not open OGG file: %s\nFile was not found or had incorrect format.\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
length = m_oggFile.length();
|
||||
m_oggFile.cleanup();
|
||||
}
|
||||
|
||||
in.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Cdrom::loadCue(const std::string& cueFile)
|
||||
{
|
||||
static const std::regex fileDirective("^\\s*FILE\\s+\"(.*)\"\\s+(\\S+)\\s*$", std::regex_constants::icase);
|
||||
static const std::regex trackDirective("^\\s*TRACK\\s+([0-9]+)\\s+(\\S*)\\s*$", std::regex_constants::icase);
|
||||
static const std::regex indexDirective("^\\s*INDEX\\s+([0-9]+)\\s+([0-9]+):([0-9]+):([0-9]+)\\s*$", std::regex_constants::icase);
|
||||
static const std::regex pregapDirective("^\\s*PREGAP\\s+([0-9]+):([0-9]+):([0-9]+)\\s*$", std::regex_constants::icase);
|
||||
|
||||
initialize();
|
||||
|
||||
std::ifstream in;
|
||||
in.open(cueFile, std::ios::in);
|
||||
if (!in.is_open())
|
||||
{
|
||||
LOG(LOG_ERROR, "Could not open CUE file: %s\n", cueFile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<std::string, cueSourceFile> sourceFiles;
|
||||
std::string currentFile;
|
||||
uint32_t currentTrack;
|
||||
TrackType currentType;
|
||||
|
||||
// On the first pass we create the toc table, but with empty position and sizes.
|
||||
// We also create a list of source files, with all trackindexes attached to them and their position in the file
|
||||
// We also probe all source files to know total size and sector size
|
||||
while (!in.eof())
|
||||
{
|
||||
std::string line;
|
||||
std::getline(in, line);
|
||||
|
||||
std::smatch matchResults;
|
||||
if (std::regex_match(line, matchResults, fileDirective))
|
||||
{
|
||||
currentFile = pathReplaceFilename(cueFile, matchResults[1].str());
|
||||
|
||||
if (sourceFiles.find(currentFile) == sourceFiles.end())
|
||||
{
|
||||
size_t length;
|
||||
uint32_t sectorSize;
|
||||
|
||||
if (!findFileLength(currentFile, length, sectorSize))
|
||||
return false;
|
||||
|
||||
sourceFiles.emplace(currentFile, cueSourceFile(sectorSize, static_cast<uint32_t>(length / sectorSize)));
|
||||
}
|
||||
}
|
||||
else if (std::regex_match(line, matchResults, trackDirective))
|
||||
{
|
||||
currentTrack = std::stoul(matchResults[1].str());
|
||||
|
||||
std::string fileType = matchResults[2].str();
|
||||
|
||||
if (stringCompareInsensitive(fileType, "MODE1/2048"))
|
||||
currentType = TrackType::Mode1_2048;
|
||||
else if (stringCompareInsensitive(fileType, "MODE1/2352"))
|
||||
currentType = TrackType::Mode1_2352;
|
||||
else if (stringCompareInsensitive(fileType, "AUDIO"))
|
||||
{
|
||||
if (fileIsImage(currentFile))
|
||||
currentType = TrackType::AudioPCM;
|
||||
else if (fileIsFlac(currentFile))
|
||||
currentType = TrackType::AudioFlac;
|
||||
else if (fileIsOgg(currentFile))
|
||||
currentType = TrackType::AudioOgg;
|
||||
else if (fileIsWav(currentFile))
|
||||
currentType = TrackType::AudioWav;
|
||||
else
|
||||
{
|
||||
LOG(LOG_ERROR, "File %s is not recognized as a supported type.\n", currentFile.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LOG_ERROR, "File type %s is unknown.\n", fileType.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (std::regex_match(line, matchResults, pregapDirective))
|
||||
{
|
||||
uint8_t m = static_cast<uint8_t>(std::stoul(matchResults[1].str()));
|
||||
uint8_t s = static_cast<uint8_t>(std::stoul(matchResults[2].str()));
|
||||
uint8_t f = static_cast<uint8_t>(std::stoul(matchResults[3].str()));
|
||||
uint32_t length = fromMSF(m, s, f);
|
||||
|
||||
m_toc.emplace_back(TocEntry("", TrackIndex(currentTrack, 0), TrackType::Silence, 0, 0, length));
|
||||
}
|
||||
else if (std::regex_match(line, matchResults, indexDirective))
|
||||
{
|
||||
uint8_t currentIndex = static_cast<uint8_t>(std::stoul(matchResults[1].str()));
|
||||
uint8_t m = static_cast<uint8_t>(std::stoul(matchResults[2].str()));
|
||||
uint8_t s = static_cast<uint8_t>(std::stoul(matchResults[3].str()));
|
||||
uint8_t f = static_cast<uint8_t>(std::stoul(matchResults[4].str()));
|
||||
uint32_t posInFile = fromMSF(m, s, f);
|
||||
|
||||
auto i = sourceFiles.find(currentFile);
|
||||
if (i == sourceFiles.end())
|
||||
{
|
||||
LOG(LOG_ERROR, "Index directive without preceding track ?\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
TrackIndex currentTrackIndex(currentTrack, currentIndex);
|
||||
|
||||
(*i).second.entries.emplace(currentTrackIndex, posInFile);
|
||||
|
||||
m_toc.emplace_back(TocEntry(currentFile, currentTrackIndex, currentType, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
in.close();
|
||||
|
||||
// Next we find out the position and size of all elements of the TOC
|
||||
|
||||
uint32_t currentPosition = 0;
|
||||
|
||||
for (TocEntry& tocEntry : m_toc)
|
||||
{
|
||||
tocEntry.lba = currentPosition;
|
||||
|
||||
// Length is already set for pregap
|
||||
if (tocEntry.entryLength == 0)
|
||||
{
|
||||
std::map<std::string, cueSourceFile>::const_iterator i;
|
||||
|
||||
for (i = sourceFiles.cbegin(); i != sourceFiles.cend(); ++i)
|
||||
{
|
||||
const cueSourceFile& sourceFile = (*i).second;
|
||||
|
||||
auto j = sourceFile.entries.find(tocEntry.trackIndex);
|
||||
if (j != sourceFile.entries.cend())
|
||||
{
|
||||
auto k = j;
|
||||
++k;
|
||||
|
||||
if (k == sourceFile.entries.cend())
|
||||
{
|
||||
tocEntry.entryLength = sourceFile.sectorCount - (*j).second;
|
||||
}
|
||||
else
|
||||
{
|
||||
tocEntry.entryLength = (*k).second - (*j).second;
|
||||
}
|
||||
|
||||
tocEntry.offset = (*j).second * sourceFile.sectorSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == sourceFiles.cend())
|
||||
{
|
||||
LOG(LOG_ERROR, "Internal error: Track not found in the source file lists.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
currentPosition += tocEntry.entryLength;
|
||||
}
|
||||
|
||||
m_leadout = currentPosition;
|
||||
|
||||
// TOC list should not be empty
|
||||
if (!m_toc.size())
|
||||
{
|
||||
LOG(LOG_ERROR, "Empty TOC! This is not supposed to happen.\n");
|
||||
initialize();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const TocEntry& entry : m_toc)
|
||||
{
|
||||
m_tocByTrack[entry.trackIndex] = &entry;
|
||||
m_tocByLBA[entry.lba] = &entry;
|
||||
}
|
||||
|
||||
seek(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::map<TrackIndex, const Cdrom::TocEntry*>& Cdrom::toc() const
|
||||
{
|
||||
return m_tocByTrack;
|
||||
}
|
||||
|
||||
TrackIndex Cdrom::currentTrackIndex() const
|
||||
{
|
||||
if (!m_currentTrack)
|
||||
return TrackIndex(0);
|
||||
|
||||
return m_currentTrack->trackIndex;
|
||||
}
|
||||
|
||||
uint32_t Cdrom::currentTrackPosition() const
|
||||
{
|
||||
if (!m_currentTrack)
|
||||
return 0;
|
||||
|
||||
return m_currentTrack->lba;
|
||||
}
|
||||
|
||||
uint32_t Cdrom::currentIndexSize() const
|
||||
{
|
||||
if (!m_currentTrack)
|
||||
return 0;
|
||||
|
||||
auto i = m_tocByTrack.find(m_currentTrack->trackIndex);
|
||||
if (i == m_tocByTrack.end())
|
||||
return 0;
|
||||
|
||||
++i;
|
||||
if (i == m_tocByTrack.end())
|
||||
return m_leadout - m_currentTrack->lba;
|
||||
|
||||
return ((*i).second->lba) - m_currentTrack->lba;
|
||||
}
|
||||
|
||||
const Cdrom::TocEntry* Cdrom::currentTrack() const
|
||||
{
|
||||
return m_currentTrack;
|
||||
}
|
||||
|
||||
uint8_t Cdrom::firstTrack() const
|
||||
{
|
||||
if (m_tocByTrack.empty())
|
||||
return 0;
|
||||
|
||||
return (*m_tocByTrack.cbegin()).second->trackIndex.track();
|
||||
}
|
||||
|
||||
uint8_t Cdrom::lastTrack() const
|
||||
{
|
||||
if (m_tocByTrack.empty())
|
||||
return 0;
|
||||
|
||||
return (*m_tocByTrack.crbegin()).second->trackIndex.track();
|
||||
}
|
||||
|
||||
uint32_t Cdrom::trackPosition(uint8_t track) const
|
||||
{
|
||||
TrackIndex index(track, 1);
|
||||
|
||||
auto i = m_tocByTrack.find(index);
|
||||
if (i == m_tocByTrack.end())
|
||||
return 0;
|
||||
|
||||
return (*i).second->lba;
|
||||
}
|
||||
|
||||
bool Cdrom::trackIsData(uint8_t track) const
|
||||
{
|
||||
TrackIndex index(track, 1);
|
||||
|
||||
auto i = m_tocByTrack.find(index);
|
||||
if (i == m_tocByTrack.end())
|
||||
return 0;
|
||||
|
||||
return ((*i).second->trackType == TrackType::Mode1_2048) || ((*i).second->trackType == TrackType::Mode1_2352);
|
||||
}
|
||||
|
||||
uint32_t Cdrom::leadout() const
|
||||
{
|
||||
return m_leadout;
|
||||
}
|
||||
|
||||
uint32_t Cdrom::position() const
|
||||
{
|
||||
return m_currentPosition;
|
||||
}
|
||||
|
||||
void Cdrom::increasePosition()
|
||||
{
|
||||
if (!m_isPlaying)
|
||||
return;
|
||||
|
||||
m_currentPosition++;
|
||||
handleTrackChange(true);
|
||||
}
|
||||
|
||||
void Cdrom::seek(uint32_t position)
|
||||
{
|
||||
m_currentPosition = position;
|
||||
|
||||
handleTrackChange(false);
|
||||
|
||||
if (!m_file.is_open())
|
||||
return;
|
||||
|
||||
seekAudio();
|
||||
}
|
||||
|
||||
void Cdrom::play()
|
||||
{
|
||||
m_isPlaying = true;
|
||||
}
|
||||
|
||||
bool Cdrom::isPlaying() const
|
||||
{
|
||||
return m_isPlaying;
|
||||
}
|
||||
|
||||
void Cdrom::stop()
|
||||
{
|
||||
m_isPlaying = false;
|
||||
}
|
||||
|
||||
void Cdrom::readData(char* buffer)
|
||||
{
|
||||
if ((!m_currentTrack) || (!isData()) || !m_file.is_open() || (m_currentPosition >= m_leadout))
|
||||
{
|
||||
std::memset(buffer, 0, 2048);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t trackOffset = m_currentPosition - m_currentTrack->lba;
|
||||
|
||||
if (m_currentTrack->trackType == TrackType::Mode1_2048)
|
||||
trackOffset *= 2048;
|
||||
else if (m_currentTrack->trackType == TrackType::Mode1_2352)
|
||||
trackOffset = (trackOffset * 2352) + 16;
|
||||
|
||||
m_file.seekg(trackOffset + m_currentTrack->offset, std::ios::beg);
|
||||
m_file.read(buffer, 2048);
|
||||
|
||||
// Fix ifstream stupidity: eof is not a failure condition
|
||||
if (m_file.fail() && m_file.eof())
|
||||
m_file.clear();
|
||||
|
||||
uint32_t done = static_cast<uint32_t>(m_file.gcount());
|
||||
if (done < 2048)
|
||||
std::memset(buffer + done, 0, 2048 - done);
|
||||
}
|
||||
|
||||
void Cdrom::audioBufferWorker()
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_workerMutex);
|
||||
m_workerConditionVariable.wait(lock, [&]{ return (m_circularBuffer.availableToWrite() && isAudio() && isPlaying()) || (m_exitFlag); });
|
||||
|
||||
if (m_exitFlag)
|
||||
break;
|
||||
|
||||
char buffer[3000];
|
||||
size_t slice = std::min(size_t(3000), m_circularBuffer.availableToWrite());
|
||||
readAudioDirect(buffer, slice);
|
||||
m_circularBuffer.push_back(buffer, slice);
|
||||
|
||||
lock.unlock();
|
||||
m_workerConditionVariable.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void Cdrom::readAudio(char* buffer, size_t size)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_workerMutex);
|
||||
m_workerConditionVariable.wait(lock, [&]{ return (m_circularBuffer.availableToRead() >= size); });
|
||||
|
||||
m_circularBuffer.pop_front(buffer, size);
|
||||
|
||||
lock.unlock();
|
||||
m_workerConditionVariable.notify_one();
|
||||
}
|
||||
|
||||
void Cdrom::readAudioDirect(char* buffer, size_t size)
|
||||
{
|
||||
if ((!m_currentTrack) || (!isAudio()) || !m_file.is_open() || (m_currentPosition >= m_leadout))
|
||||
{
|
||||
std::memset(buffer, 0, size);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t done = 0;
|
||||
|
||||
if (m_currentTrack->trackType == TrackType::AudioPCM)
|
||||
{
|
||||
m_file.read(buffer, size);
|
||||
|
||||
// Fix ifstream stupidity: eof is not a failure condition
|
||||
if (m_file.fail() && m_file.eof())
|
||||
m_file.clear();
|
||||
|
||||
done = static_cast<size_t>(m_file.gcount());
|
||||
}
|
||||
else if (m_currentTrack->trackType == TrackType::AudioFlac)
|
||||
{
|
||||
done = m_flacFile.read(buffer, size);
|
||||
}
|
||||
else if (m_currentTrack->trackType == TrackType::AudioOgg)
|
||||
{
|
||||
done = m_oggFile.read(buffer, size);
|
||||
}
|
||||
else if (m_currentTrack->trackType == TrackType::AudioWav)
|
||||
{
|
||||
done = m_wavFile.read(buffer, size);
|
||||
}
|
||||
|
||||
if (done < size)
|
||||
{
|
||||
std::memset(buffer + done, 0, size - done);
|
||||
}
|
||||
}
|
||||
|
||||
bool Cdrom::isData() const
|
||||
{
|
||||
if (!m_currentTrack)
|
||||
return false;
|
||||
|
||||
return (m_currentTrack->trackType == TrackType::Mode1_2048) || (m_currentTrack->trackType == TrackType::Mode1_2352);
|
||||
}
|
||||
|
||||
bool Cdrom::isAudio() const
|
||||
{
|
||||
if (!m_currentTrack)
|
||||
return false;
|
||||
|
||||
return !isData();
|
||||
}
|
||||
|
||||
bool Cdrom::isPregap() const
|
||||
{
|
||||
if (!m_currentTrack)
|
||||
return false;
|
||||
|
||||
return (m_currentTrack->trackIndex.index() == 0);
|
||||
}
|
||||
|
||||
bool Cdrom::isTocEmpty() const
|
||||
{
|
||||
return (m_toc.size() == 0);
|
||||
}
|
||||
|
||||
void Cdrom::cleanup()
|
||||
{
|
||||
if (m_file.is_open())
|
||||
m_file.close();
|
||||
}
|
||||
|
||||
bool Cdrom::hasFileChanged(const TocEntry* current) const
|
||||
{
|
||||
if (!m_currentTrack)
|
||||
return true;
|
||||
|
||||
if (m_currentTrack->trackIndex == current->trackIndex)
|
||||
return false;
|
||||
|
||||
return m_currentTrack->file != current->file;
|
||||
}
|
||||
|
||||
void Cdrom::handleTrackChange(bool doInitialSeek)
|
||||
{
|
||||
if (!m_tocByLBA.size())
|
||||
return;
|
||||
|
||||
auto i = m_tocByLBA.upper_bound(m_currentPosition);
|
||||
--i;
|
||||
const TocEntry* current = i->second;
|
||||
|
||||
if (current == m_currentTrack)
|
||||
return;
|
||||
|
||||
if (!hasFileChanged(current))
|
||||
{
|
||||
m_currentTrack = current;
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_workerMutex);
|
||||
|
||||
m_circularBuffer.clear();
|
||||
|
||||
cleanup();
|
||||
|
||||
m_currentTrack = current;
|
||||
|
||||
if (m_currentTrack->trackType == TrackType::Silence)
|
||||
{
|
||||
lock.unlock();
|
||||
m_workerConditionVariable.notify_one();
|
||||
return;
|
||||
}
|
||||
|
||||
m_file.open(m_currentTrack->file, std::ios::in | std::ios::binary);
|
||||
|
||||
if (m_currentTrack->trackType == TrackType::AudioFlac)
|
||||
m_flacFile.initialize(&m_file);
|
||||
else if (m_currentTrack->trackType == TrackType::AudioOgg)
|
||||
m_oggFile.initialize(&m_file);
|
||||
else if (m_currentTrack->trackType == TrackType::AudioWav)
|
||||
m_wavFile.initialize(&m_file);
|
||||
|
||||
if (doInitialSeek)
|
||||
{
|
||||
lock.unlock();
|
||||
seekAudio();
|
||||
}
|
||||
}
|
||||
|
||||
void Cdrom::seekAudio()
|
||||
{
|
||||
if ((!m_currentTrack) || (!m_file.is_open()) || !isAudio())
|
||||
return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_workerMutex);
|
||||
|
||||
m_circularBuffer.clear();
|
||||
|
||||
uint32_t trackOffset = (std::min(m_currentPosition, m_leadout - 1) - m_currentTrack->lba) * 2352;
|
||||
|
||||
// Now seek according to the track type
|
||||
if (m_currentTrack->trackType == TrackType::AudioPCM)
|
||||
m_file.seekg(trackOffset + m_currentTrack->offset);
|
||||
else if (m_currentTrack->trackType == TrackType::AudioFlac)
|
||||
m_flacFile.seek(trackOffset + m_currentTrack->offset);
|
||||
else if (m_currentTrack->trackType == TrackType::AudioOgg)
|
||||
m_oggFile.seek(trackOffset + m_currentTrack->offset);
|
||||
else if (m_currentTrack->trackType == TrackType::AudioWav)
|
||||
m_wavFile.seek(trackOffset + m_currentTrack->offset);
|
||||
|
||||
lock.unlock();
|
||||
m_workerConditionVariable.notify_one();
|
||||
}
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const Cdrom& cdrom)
|
||||
{
|
||||
out << cdrom.m_currentPosition;
|
||||
out << cdrom.m_isPlaying;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
DataPacker& operator>>(DataPacker& in, Cdrom& cdrom)
|
||||
{
|
||||
in >> cdrom.m_currentPosition;
|
||||
in >> cdrom.m_isPlaying;
|
||||
|
||||
neocd.cdrom.seek(cdrom.m_currentPosition);
|
||||
|
||||
return in;
|
||||
}
|
387
src/cdrom.h
Normal file
387
src/cdrom.h
Normal file
@ -0,0 +1,387 @@
|
||||
#ifndef CDROM_H
|
||||
#define CDROM_H
|
||||
|
||||
#include "trackindex.h"
|
||||
#include "flacfile.h"
|
||||
#include "oggfile.h"
|
||||
#include "wavfile.h"
|
||||
#include "circularbuffer.h"
|
||||
#include "datapacker.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
/**
|
||||
* @class Cdrom
|
||||
* @brief The Cdrom class holds the CD image table of contents and is also responsible for reading and decoding audio
|
||||
* data in a separate thread
|
||||
*/
|
||||
class Cdrom
|
||||
{
|
||||
public:
|
||||
/// Enum representing all track types supported by the emulator
|
||||
enum class TrackType
|
||||
{
|
||||
Mode1_2352, /// Raw track (2352 bytes per sector)
|
||||
Mode1_2048, /// ISO track (2048 bytes per sector)
|
||||
Silence, /// Audio silence (no associated data)
|
||||
AudioPCM, /// PCM audio (raw track)
|
||||
AudioFlac, /// FLAC audio
|
||||
AudioOgg, /// Ogg audio
|
||||
AudioWav /// WAV audio
|
||||
};
|
||||
|
||||
/**
|
||||
* @class TocEntry
|
||||
* @brief Structure holding information about a TOC entry
|
||||
*/
|
||||
class TocEntry
|
||||
{
|
||||
public:
|
||||
TocEntry(const std::string& f, const TrackIndex& ti, TrackType tt, uint32_t p, uint32_t o, uint32_t l) :
|
||||
file(f),
|
||||
trackIndex(ti),
|
||||
trackType(tt),
|
||||
lba(p),
|
||||
offset(o),
|
||||
entryLength(l)
|
||||
{}
|
||||
|
||||
/// File associated to this TOC entry
|
||||
std::string file;
|
||||
|
||||
/// Track index
|
||||
TrackIndex trackIndex;
|
||||
|
||||
/**
|
||||
* Track type
|
||||
* @sa Cdrom::TrackType
|
||||
*/
|
||||
TrackType trackType;
|
||||
|
||||
/// Starting sector
|
||||
uint32_t lba;
|
||||
|
||||
/// Start offset of the track in the file (in bytes)
|
||||
uint32_t offset;
|
||||
|
||||
/// Track length (in sectors)
|
||||
uint32_t entryLength;
|
||||
};
|
||||
|
||||
Cdrom();
|
||||
~Cdrom();
|
||||
|
||||
// Non copyable
|
||||
Cdrom(const Cdrom&) = delete;
|
||||
|
||||
// Non copyable
|
||||
Cdrom& operator=(const Cdrom&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Create the worker thread to decode audio
|
||||
*/
|
||||
void createWorkerThread();
|
||||
|
||||
/**
|
||||
* @brief Ask the audio decoder thread to exit and wait for it to finish
|
||||
*/
|
||||
void endWorkerThread();
|
||||
|
||||
/**
|
||||
* @brief Initialize all members to a known state
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* @brief Reset the emulated CD-ROM
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief Determine the length of a file, in bytes and in sectors.
|
||||
* @note If the file is compressed audio, the values are calculated from the uncompressed size.
|
||||
* @param[in] filename Name of the file for which we need the length
|
||||
* @param[out] length Will contain the length in bytes
|
||||
* @param[out] sectorSize Will contain the length in sectors
|
||||
*/
|
||||
bool findFileLength(const std::string& filename, size_t& length, uint32_t& sectorSize);
|
||||
|
||||
/**
|
||||
* @brief Load a cue sheet file
|
||||
* @param cueFile File to load the CD-ROM TOC from
|
||||
* @return true if loading succeeded
|
||||
*/
|
||||
bool loadCue(const std::string& cueFile);
|
||||
|
||||
/**
|
||||
* @brief Get the CD-ROM table of contents
|
||||
* @return Map of TocEntry sorted by TrackIndex
|
||||
*/
|
||||
const std::map<TrackIndex, const TocEntry*>& toc() const;
|
||||
|
||||
/**
|
||||
* @brief Get a pointer to the TocEntry of the current track
|
||||
*/
|
||||
const TocEntry* currentTrack() const;
|
||||
|
||||
/**
|
||||
* @brief Get the TrackIndex of the current track
|
||||
*/
|
||||
TrackIndex currentTrackIndex() const;
|
||||
|
||||
/**
|
||||
* @brief Get the start sector of the current track
|
||||
*/
|
||||
uint32_t currentTrackPosition() const;
|
||||
|
||||
/**
|
||||
* @brief Get the size in sectors of the current index
|
||||
*/
|
||||
uint32_t currentIndexSize() const;
|
||||
|
||||
/**
|
||||
* @brief Get the track number of the first track
|
||||
*/
|
||||
uint8_t firstTrack() const;
|
||||
|
||||
/**
|
||||
* @brief Get the track number of the last track
|
||||
*/
|
||||
uint8_t lastTrack() const;
|
||||
|
||||
/**
|
||||
* @brief Get the start sector of track 'track'.
|
||||
* @param track Track number.
|
||||
* @return The start sector for the track, or 0.
|
||||
*/
|
||||
uint32_t trackPosition(uint8_t track) const;
|
||||
|
||||
/**
|
||||
* @brief Check if the track 'track' is a data track.
|
||||
* @param track Track number.
|
||||
* @return True if the track is of data type.
|
||||
*/
|
||||
bool trackIsData(uint8_t track) const;
|
||||
|
||||
/**
|
||||
* @brief Return the position of the CD leadout.
|
||||
*/
|
||||
uint32_t leadout() const;
|
||||
|
||||
/**
|
||||
* @brief Return the current play position.
|
||||
*/
|
||||
uint32_t position() const;
|
||||
|
||||
/**
|
||||
* @brief Move the play position to the next sector (if playing).
|
||||
*/
|
||||
void increasePosition();
|
||||
|
||||
/**
|
||||
* @brief Change the current play position. This will handle track change and discard buffered audio.
|
||||
* @param position New play position.
|
||||
*/
|
||||
void seek(uint32_t position);
|
||||
|
||||
/**
|
||||
* @brief Start playing at the current position
|
||||
*/
|
||||
void play();
|
||||
|
||||
/**
|
||||
* @brief Returns true if the CD-ROM is playing
|
||||
*/
|
||||
bool isPlaying() const;
|
||||
|
||||
/**
|
||||
* @brief Stop playing
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* @brief Read a data sector from CD-ROM. (2048 bytes)
|
||||
* @note Returns an empty section if the current position does not belong to a data track.
|
||||
* @param buffer The buffer to write to.
|
||||
*/
|
||||
void readData(char *buffer);
|
||||
|
||||
/**
|
||||
* @brief Read audio data decoded by the audio decoder thread, blocking if needed.
|
||||
* @note When the CD-ROM is not playing the worker thread keeps filling the buffer with silence.
|
||||
* @param buffer The buffer to write to.
|
||||
* @param size Size of the data to get.
|
||||
*/
|
||||
void readAudio(char *buffer, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Read audio data from the image file and decode it. Should only be called from the decoder thread!
|
||||
* @param buffer The buffer to write to.
|
||||
* @param size The size to srite.
|
||||
*/
|
||||
void readAudioDirect(char *buffer, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Return true if the current position belongs to a data track.
|
||||
*/
|
||||
bool isData() const;
|
||||
|
||||
/**
|
||||
* @brief Return true if the current position belongs to an audio track.
|
||||
*/
|
||||
bool isAudio() const;
|
||||
|
||||
/**
|
||||
* @brief Return true if the current position is pregap (silence)
|
||||
*/
|
||||
bool isPregap() const;
|
||||
|
||||
/**
|
||||
* @brief Returns true if the TOC is empty (invalid).
|
||||
*/
|
||||
bool isTocEmpty() const;
|
||||
|
||||
/**
|
||||
* @brief Decode a BCD value.
|
||||
* @param value Value to decode.
|
||||
*/
|
||||
static inline constexpr uint8_t fromBCD(uint8_t value)
|
||||
{
|
||||
return (((value >> 4) * 10) + (value & 0x0F));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encode a value to BCD.
|
||||
* @param value Value to encode.
|
||||
*/
|
||||
static inline constexpr uint8_t toBCD(uint8_t value)
|
||||
{
|
||||
return (((value / 10) << 4) | (value % 10));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a logical block address to sector number.
|
||||
* @param lba Logical Block Address.
|
||||
*/
|
||||
static inline constexpr uint32_t fromLBA(uint32_t lba)
|
||||
{
|
||||
return (lba + 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a sector number to logical block address.
|
||||
* @param position Sector number.
|
||||
*/
|
||||
static inline constexpr uint32_t toLBA(uint32_t position)
|
||||
{
|
||||
return (position - 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a sector number to minutes, seconds, frames.
|
||||
* @param[in] position The position to convert.
|
||||
* @param[out] m Minutes.
|
||||
* @param[out] s Seconds.
|
||||
* @param[out] f Frames.
|
||||
*/
|
||||
static inline void toMSF(uint32_t position, uint8_t& m, uint8_t& s, uint8_t& f)
|
||||
{
|
||||
m = position / 4500;
|
||||
s = (position / 75) % 60;
|
||||
f = position % 75;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a position in minutes, seconds, frames to a sector number
|
||||
* @param m Minutes.
|
||||
* @param s Seconds.
|
||||
* @param f Frames.
|
||||
* @return Sector Number.
|
||||
*/
|
||||
static inline constexpr uint32_t fromMSF(uint8_t m, uint8_t s, uint8_t f)
|
||||
{
|
||||
return (m * 4500) + (s * 75) + f;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* @brief Close the image / audio file if needed.
|
||||
*/
|
||||
void cleanup();
|
||||
|
||||
/**
|
||||
* @brief Returns true if the file corresponding to the TocEntry is different from the one currently open.
|
||||
* @param current The TocEntry
|
||||
*/
|
||||
bool hasFileChanged(const TocEntry* current) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Open the image / audio file corresponding to the current track, and immediately seek audio if 'doInitialSeek' is true.
|
||||
* @param doInitialSeek If true, see audio immediately.
|
||||
*/
|
||||
void handleTrackChange(bool doInitialSeek);
|
||||
|
||||
/**
|
||||
* @brief Seek in the audio file to the position corresponding to m_currentPosition.
|
||||
*/
|
||||
void seekAudio();
|
||||
|
||||
/**
|
||||
* @brief Audio decoding function. Runs in a separate thread.
|
||||
*/
|
||||
void audioBufferWorker();
|
||||
|
||||
// **** START Variables to save in savestate
|
||||
|
||||
uint32_t m_currentPosition; /// The sector being played / decoded
|
||||
bool m_isPlaying; /// True if the CD-ROM is playing
|
||||
|
||||
// **** END Variables to save in savestate
|
||||
|
||||
const TocEntry* m_currentTrack; /// TocEntry pointer to the current track
|
||||
|
||||
CircularBuffer<char> m_circularBuffer; /// Circular buffer to store decoded audio
|
||||
bool m_audioWorkerThreadCreated; /// True is the audio decoder thread has been created
|
||||
bool m_exitFlag; /// Set to true to have the audio thread stop
|
||||
std::thread m_workerThread; /// Audio decoder worker thread
|
||||
std::mutex m_workerMutex; /// Mutex to access the circular buffer
|
||||
std::condition_variable m_workerConditionVariable; /// Condition variable to notify when more data is available
|
||||
|
||||
std::ifstream m_file; /// The currently opened image cd image file
|
||||
FlacFile m_flacFile; /// FLAC file decoder
|
||||
OggFile m_oggFile; /// OGG file decoder
|
||||
WavFile m_wavFile; /// WAV file decoder
|
||||
|
||||
uint32_t m_leadout; /// Sector number of the lead out area (end of the CD)
|
||||
std::vector<TocEntry> m_toc; /// CD-ROM table of contents
|
||||
std::map<TrackIndex, const TocEntry*> m_tocByTrack; /// Pointers to the TOC entries, sorted by track index
|
||||
std::map<uint32_t, const TocEntry*> m_tocByLBA; /// Pointers to the TOC entries, sorted by logical block address
|
||||
|
||||
friend DataPacker& operator<<(DataPacker& out, const Cdrom& cdrom);
|
||||
friend DataPacker& operator>>(DataPacker& in, Cdrom& cdrom);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Save CD-ROM state to a DataPacker
|
||||
* @param out DataPacker to save to
|
||||
* @param cdrom The cdrom state
|
||||
*/
|
||||
DataPacker& operator<<(DataPacker& out, const Cdrom& cdrom);
|
||||
|
||||
/**
|
||||
* @brief Restore CD-ROM state from a DataPacker
|
||||
* @param in DataPacker to read state from
|
||||
* @param cdrom The cdrom state
|
||||
*/
|
||||
DataPacker& operator>>(DataPacker& in, Cdrom& cdrom);
|
||||
|
||||
#endif // CDROM_H
|
238
src/circularbuffer.h
Normal file
238
src/circularbuffer.h
Normal file
@ -0,0 +1,238 @@
|
||||
#ifndef CIRCULARBUFFER_H
|
||||
#define CIRCULARBUFFER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
|
||||
template<class T>
|
||||
class CircularBuffer
|
||||
{
|
||||
public:
|
||||
explicit CircularBuffer() : m_frontIndex(0), m_backIndex(0), m_capacity(0), m_dataSize(0), m_buffer(nullptr)
|
||||
{}
|
||||
|
||||
virtual ~CircularBuffer()
|
||||
{
|
||||
releaseBuffer();
|
||||
}
|
||||
// Non copyable
|
||||
CircularBuffer(const CircularBuffer&) = delete;
|
||||
|
||||
// Non copyable
|
||||
CircularBuffer& operator=(const CircularBuffer&) = delete;
|
||||
|
||||
size_t capacity() const
|
||||
{
|
||||
return m_capacity;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return (availableToRead() == 0);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_dataSize = 0;
|
||||
m_frontIndex = 0;
|
||||
m_backIndex = 0;
|
||||
}
|
||||
|
||||
void set_capacity(size_t newCapacity)
|
||||
{
|
||||
assert(newCapacity > 0);
|
||||
|
||||
if (newCapacity == m_capacity)
|
||||
return;
|
||||
|
||||
releaseBuffer();
|
||||
m_capacity = newCapacity;
|
||||
m_buffer = reinterpret_cast<T*>(std::malloc(m_capacity * sizeof(T)));
|
||||
m_dataSize = 0;
|
||||
m_frontIndex = 0;
|
||||
m_backIndex = 0;
|
||||
}
|
||||
|
||||
size_t availableToRead() const
|
||||
{
|
||||
return m_dataSize;
|
||||
}
|
||||
|
||||
size_t availableToWrite() const
|
||||
{
|
||||
return m_capacity - availableToRead();
|
||||
}
|
||||
|
||||
void push_back(const T& val)
|
||||
{
|
||||
assert(availableToWrite() != 0);
|
||||
m_buffer[m_backIndex] = val;
|
||||
m_dataSize++;
|
||||
incrementIndex(m_backIndex, 1);
|
||||
}
|
||||
|
||||
void push_back(const T* ptr, size_t num)
|
||||
{
|
||||
assert(availableToWrite() >= num);
|
||||
size_t toWrapAround = m_capacity - m_backIndex;
|
||||
|
||||
if (num > toWrapAround)
|
||||
{
|
||||
std::memcpy(&m_buffer[m_backIndex], ptr, toWrapAround * sizeof(T));
|
||||
m_backIndex = 0;
|
||||
m_dataSize += toWrapAround;
|
||||
ptr += toWrapAround;
|
||||
num -= toWrapAround;
|
||||
}
|
||||
|
||||
std::memcpy(&m_buffer[m_backIndex], ptr, num * sizeof(T));
|
||||
incrementIndex(m_backIndex, num);
|
||||
m_dataSize += num;
|
||||
}
|
||||
|
||||
void push_front(const T& val)
|
||||
{
|
||||
assert(availableToWrite() != 0);
|
||||
decrementIndex(m_frontIndex, 1);
|
||||
m_dataSize++;
|
||||
m_buffer[m_frontIndex] = val;
|
||||
}
|
||||
|
||||
void push_front(const T* ptr, size_t num)
|
||||
{
|
||||
assert(availableToWrite() >= num);
|
||||
|
||||
decrementIndex(m_frontIndex, num);
|
||||
|
||||
size_t idx = m_frontIndex;
|
||||
size_t toWrapAround = m_capacity - idx;
|
||||
|
||||
if (num > toWrapAround)
|
||||
{
|
||||
std::memcpy(&m_buffer[idx], ptr, toWrapAround * sizeof(T));
|
||||
idx = 0;
|
||||
m_dataSize += toWrapAround;
|
||||
ptr += toWrapAround;
|
||||
num -= toWrapAround;
|
||||
}
|
||||
|
||||
std::memcpy(&m_buffer[idx], ptr, num * sizeof(T));
|
||||
m_dataSize += num;
|
||||
}
|
||||
|
||||
const T& front() const
|
||||
{
|
||||
assert(availableToRead() != 0);
|
||||
return m_buffer[m_frontIndex];
|
||||
}
|
||||
|
||||
const T& back() const
|
||||
{
|
||||
assert(availableToRead() != 0);
|
||||
size_t n = m_backIndex;
|
||||
decrementIndex(n, 1);
|
||||
return m_buffer[n];
|
||||
}
|
||||
|
||||
const T& at(size_t n) const
|
||||
{
|
||||
assert(n < availableToRead());
|
||||
incrementIndex(n, m_frontIndex);
|
||||
return m_buffer[n];
|
||||
}
|
||||
|
||||
void pop_back()
|
||||
{
|
||||
assert(availableToRead() != 0);
|
||||
m_dataSize--;
|
||||
decrementIndex(m_backIndex, 1);
|
||||
}
|
||||
|
||||
void pop_back(T* dst, size_t num)
|
||||
{
|
||||
assert(availableToRead() >= num);
|
||||
|
||||
decrementIndex(m_backIndex, num);
|
||||
|
||||
size_t idx = m_backIndex;
|
||||
size_t toWrapAround = m_capacity - idx;
|
||||
|
||||
if (num > toWrapAround)
|
||||
{
|
||||
std::memcpy(dst, &m_buffer[idx], toWrapAround * sizeof(T));
|
||||
idx = 0;
|
||||
m_dataSize -= toWrapAround;
|
||||
dst += toWrapAround;
|
||||
num -= toWrapAround;
|
||||
}
|
||||
|
||||
std::memcpy(dst, &m_buffer[idx], num * sizeof(T));
|
||||
m_dataSize -= num;
|
||||
}
|
||||
|
||||
void pop_front()
|
||||
{
|
||||
assert(availableToRead() != 0);
|
||||
m_dataSize--;
|
||||
incrementIndex(m_frontIndex, 1);
|
||||
}
|
||||
|
||||
void pop_front(T* dst, size_t num)
|
||||
{
|
||||
assert(availableToRead() >= num);
|
||||
|
||||
size_t toWrapAround = m_capacity - m_frontIndex;
|
||||
|
||||
if (num > toWrapAround)
|
||||
{
|
||||
std::memcpy(dst, &m_buffer[m_frontIndex], toWrapAround * sizeof(T));
|
||||
m_frontIndex = 0;
|
||||
m_dataSize -= toWrapAround;
|
||||
dst += toWrapAround;
|
||||
num -= toWrapAround;
|
||||
}
|
||||
|
||||
std::memcpy(dst, &m_buffer[m_frontIndex], num * sizeof(T));
|
||||
incrementIndex(m_frontIndex, num);
|
||||
m_dataSize -= num;
|
||||
}
|
||||
|
||||
protected:
|
||||
void releaseBuffer()
|
||||
{
|
||||
if (m_buffer)
|
||||
{
|
||||
std::free(m_buffer);
|
||||
m_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
inline void incrementIndex(size_t& index, size_t displacement) const
|
||||
{
|
||||
assert(m_capacity != 0);
|
||||
index = (index + displacement) % m_capacity;
|
||||
}
|
||||
|
||||
inline void decrementIndex(size_t& index, size_t displacement) const
|
||||
{
|
||||
assert(m_capacity != 0);
|
||||
index = (index - displacement - 1) % m_capacity;
|
||||
}
|
||||
|
||||
inline size_t indexDistance(const size_t& index1, const size_t& index2) const
|
||||
{
|
||||
if (index2 >= index1)
|
||||
return index2 - index1;
|
||||
|
||||
return (m_capacity - index1) + index2;
|
||||
}
|
||||
|
||||
size_t m_capacity;
|
||||
size_t m_dataSize;
|
||||
size_t m_frontIndex;
|
||||
size_t m_backIndex;
|
||||
T* m_buffer;
|
||||
};
|
||||
|
||||
#endif // CIRCULARBUFFER_H
|
351
src/datapacker.cpp
Normal file
351
src/datapacker.cpp
Normal file
@ -0,0 +1,351 @@
|
||||
#include "datapacker.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
DataPacker::DataPacker() :
|
||||
m_data(nullptr),
|
||||
m_size(0),
|
||||
m_capacity(0),
|
||||
m_granularity(1024),
|
||||
m_readPosition(0),
|
||||
m_state(true),
|
||||
m_ownBuffer(true)
|
||||
{ }
|
||||
|
||||
DataPacker::DataPacker(DataPacker&& other) :
|
||||
DataPacker()
|
||||
{
|
||||
swap(other);
|
||||
}
|
||||
|
||||
DataPacker::DataPacker(
|
||||
char *data,
|
||||
size_t size,
|
||||
size_t capacity) :
|
||||
m_data(data),
|
||||
m_size(size),
|
||||
m_capacity(capacity),
|
||||
m_granularity(1024),
|
||||
m_readPosition(0),
|
||||
m_state(true),
|
||||
m_ownBuffer(false)
|
||||
{ }
|
||||
|
||||
DataPacker::~DataPacker()
|
||||
{
|
||||
if (m_data && m_ownBuffer)
|
||||
std::free(m_data);
|
||||
}
|
||||
|
||||
char *DataPacker::begin()
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
const char *DataPacker::cbegin() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
char *DataPacker::end()
|
||||
{
|
||||
return m_data + m_size;
|
||||
}
|
||||
|
||||
const char *DataPacker::cend() const
|
||||
{
|
||||
return m_data + m_size;
|
||||
}
|
||||
|
||||
const char *DataPacker::data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
bool DataPacker::empty() const
|
||||
{
|
||||
return (m_size == 0);
|
||||
}
|
||||
|
||||
size_t DataPacker::size() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
bool DataPacker::resize(size_t newSize)
|
||||
{
|
||||
if (!m_ownBuffer)
|
||||
{
|
||||
m_size = std::min(newSize, m_capacity);
|
||||
return (newSize <= m_capacity);
|
||||
}
|
||||
|
||||
if (newSize == m_size)
|
||||
return true;
|
||||
|
||||
reserve(newSize);
|
||||
m_size = newSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DataPacker::reserve(size_t size)
|
||||
{
|
||||
if (!m_ownBuffer)
|
||||
return;
|
||||
|
||||
size_t newCapacity;
|
||||
|
||||
if (size % m_granularity)
|
||||
newCapacity = ((size / m_granularity) + 1) * m_granularity;
|
||||
else
|
||||
newCapacity = size;
|
||||
|
||||
if (newCapacity <= m_capacity)
|
||||
return;
|
||||
|
||||
m_capacity = newCapacity;
|
||||
|
||||
if (m_data)
|
||||
m_data = reinterpret_cast<char *>(std::realloc(m_data, m_capacity));
|
||||
else
|
||||
m_data = reinterpret_cast<char *>(std::malloc(m_capacity));
|
||||
}
|
||||
|
||||
void DataPacker::shrink_to_fit()
|
||||
{
|
||||
if (!m_ownBuffer)
|
||||
return;
|
||||
|
||||
if (m_size == m_capacity)
|
||||
return;
|
||||
|
||||
if (m_size)
|
||||
{
|
||||
m_data = reinterpret_cast<char *>(std::realloc(m_data, m_size));
|
||||
m_capacity = m_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::free(m_data);
|
||||
m_data = nullptr;
|
||||
m_capacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DataPacker::clear()
|
||||
{
|
||||
if (m_ownBuffer)
|
||||
{
|
||||
if (m_data)
|
||||
std::free(m_data);
|
||||
|
||||
m_data = nullptr;
|
||||
m_capacity = 0;
|
||||
}
|
||||
|
||||
m_size = 0;
|
||||
m_granularity = 1024;
|
||||
m_readPosition = 0;
|
||||
m_state = true;
|
||||
}
|
||||
|
||||
void DataPacker::swap(DataPacker &other)
|
||||
{
|
||||
std::swap(m_data, other.m_data);
|
||||
std::swap(m_size, other.m_size);
|
||||
std::swap(m_capacity, other.m_capacity);
|
||||
std::swap(m_granularity, other.m_granularity);
|
||||
std::swap(m_readPosition, other.m_readPosition);
|
||||
std::swap(m_state, other.m_state);
|
||||
std::swap(m_ownBuffer, other.m_ownBuffer);
|
||||
}
|
||||
|
||||
size_t DataPacker::tellp() const
|
||||
{
|
||||
return m_readPosition;
|
||||
}
|
||||
|
||||
void DataPacker::seekp(const size_t &offset, std::ios_base::seekdir way)
|
||||
{
|
||||
int64_t newOffset;
|
||||
|
||||
switch (way)
|
||||
{
|
||||
case std::ios_base::cur:
|
||||
newOffset = m_readPosition;
|
||||
newOffset += offset;
|
||||
break;
|
||||
case std::ios_base::end:
|
||||
newOffset = m_size;
|
||||
newOffset -= offset;
|
||||
break;
|
||||
default:
|
||||
newOffset = offset;
|
||||
break;
|
||||
}
|
||||
|
||||
newOffset = std::max(
|
||||
static_cast<int64_t>(std::numeric_limits<size_t>::min()),
|
||||
std::min(newOffset, static_cast<int64_t>(std::numeric_limits<size_t>::max())));
|
||||
|
||||
m_readPosition = static_cast<size_t>(newOffset);
|
||||
}
|
||||
|
||||
void DataPacker::push(const char *data, size_t size)
|
||||
{
|
||||
size_t oldSize = m_size;
|
||||
if (!resize(m_size + size))
|
||||
{
|
||||
m_state = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(m_data + oldSize, data, size);
|
||||
}
|
||||
|
||||
void DataPacker::pop(char *data, size_t size)
|
||||
{
|
||||
if (m_readPosition + size > m_size)
|
||||
{
|
||||
m_state = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(data, m_data + m_readPosition, size);
|
||||
m_readPosition += size;
|
||||
}
|
||||
|
||||
bool DataPacker::pushPointer(const char *ptr, const char *base, uint32_t maxValue)
|
||||
{
|
||||
uint32_t offset;
|
||||
|
||||
if (!ptr)
|
||||
offset = 0xFFFFFFFF;
|
||||
else
|
||||
{
|
||||
if (ptr < base)
|
||||
{
|
||||
m_state = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
offset = static_cast<uint32_t>(ptr - base);
|
||||
|
||||
if (offset > maxValue)
|
||||
{
|
||||
m_state = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
push(reinterpret_cast<char *>(&offset), sizeof(uint32_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *DataPacker::popPointerConst(const char *base, uint32_t maxValue)
|
||||
{
|
||||
uint32_t offset;
|
||||
*this >> offset;
|
||||
|
||||
if (fail())
|
||||
return nullptr;
|
||||
|
||||
if (offset == 0xFFFFFFFF)
|
||||
return nullptr;
|
||||
|
||||
if (offset > maxValue)
|
||||
{
|
||||
m_state = false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return base + offset;
|
||||
}
|
||||
|
||||
char *DataPacker::popPointer(char *base, uint32_t maxValue)
|
||||
{
|
||||
return const_cast<char *>(popPointerConst(base, maxValue));
|
||||
}
|
||||
|
||||
bool DataPacker::pushPointerMulti(const char *ptr, const DataPacker::ConstPointerMap *pointerMap, size_t mapSize)
|
||||
{
|
||||
uint32_t offset;
|
||||
|
||||
if (!ptr)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
offset = 0xFFFFFFFF;
|
||||
*this << i;
|
||||
*this << offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < mapSize; ++i)
|
||||
{
|
||||
if (ptr < pointerMap[i].base)
|
||||
continue;
|
||||
|
||||
offset = static_cast<uint32_t>(ptr - pointerMap[i].base);
|
||||
|
||||
if (offset >= pointerMap[i].size)
|
||||
continue;
|
||||
|
||||
*this << i;
|
||||
*this << offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_state = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *DataPacker::popPointerMultiConst(const ConstPointerMap *pointerMap, size_t size)
|
||||
{
|
||||
uint32_t i;
|
||||
uint32_t offset;
|
||||
|
||||
*this >> i;
|
||||
*this >> offset;
|
||||
|
||||
if (fail())
|
||||
return nullptr;
|
||||
|
||||
if (i >= size)
|
||||
{
|
||||
m_state = false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (offset == 0xFFFFFFFF)
|
||||
return nullptr;
|
||||
|
||||
if (offset >= pointerMap[i].size)
|
||||
{
|
||||
m_state = false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return pointerMap[i].base + offset;
|
||||
}
|
||||
|
||||
char *DataPacker::popPointerMulti(const DataPacker::PointerMap *pointerMap, size_t size)
|
||||
{
|
||||
return const_cast<char *>(popPointerMultiConst(reinterpret_cast<const DataPacker::ConstPointerMap *>(pointerMap), size));
|
||||
}
|
||||
|
||||
bool DataPacker::fail() const
|
||||
{
|
||||
return (!m_state);
|
||||
}
|
||||
|
||||
void DataPacker::setstate(bool state)
|
||||
{
|
||||
m_state = state;
|
||||
}
|
||||
|
||||
void DataPacker::clearstate()
|
||||
{
|
||||
m_state = true;
|
||||
}
|
94
src/datapacker.h
Normal file
94
src/datapacker.h
Normal file
@ -0,0 +1,94 @@
|
||||
#ifndef DATAPACKER_H
|
||||
#define DATAPACKER_H
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
class DataPacker
|
||||
{
|
||||
public:
|
||||
struct PointerMap
|
||||
{
|
||||
char* base;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct ConstPointerMap
|
||||
{
|
||||
const char* base;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
DataPacker();
|
||||
|
||||
DataPacker(DataPacker&& other);
|
||||
|
||||
DataPacker(char* data, size_t size, size_t capacity);
|
||||
|
||||
~DataPacker();
|
||||
|
||||
char* begin();
|
||||
const char* cbegin() const;
|
||||
char* end();
|
||||
const char* cend() const;
|
||||
|
||||
const char * data() const;
|
||||
|
||||
bool empty() const;
|
||||
|
||||
size_t size() const;
|
||||
bool resize(size_t newSize);
|
||||
void reserve(size_t size);
|
||||
void shrink_to_fit();
|
||||
|
||||
void clear();
|
||||
|
||||
void swap(DataPacker& other);
|
||||
|
||||
size_t tellp() const;
|
||||
void seekp(const size_t &offset, std::ios_base::seekdir way);
|
||||
|
||||
void push(const char* data, size_t size);
|
||||
void pop(char* data, size_t size);
|
||||
|
||||
bool pushPointer(const char* ptr, const char* base, uint32_t maxValue);
|
||||
const char *popPointerConst(const char* base, uint32_t maxValue);
|
||||
char *popPointer(char* base, uint32_t maxValue);
|
||||
|
||||
bool pushPointerMulti(const char* ptr, const ConstPointerMap* pointerMap, size_t mapSize);
|
||||
const char* popPointerMultiConst(const ConstPointerMap* pointerMap, size_t size);
|
||||
char* popPointerMulti(const PointerMap *pointerMap, size_t size);
|
||||
|
||||
bool fail() const;
|
||||
void setstate(bool state);
|
||||
void clearstate();
|
||||
|
||||
template<class T>
|
||||
DataPacker& operator<<(const T& value)
|
||||
{
|
||||
push(reinterpret_cast<const char*>(&value), sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
DataPacker& operator>>(T& value)
|
||||
{
|
||||
pop(reinterpret_cast<char*>(&value), sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPacker(const DataPacker& other) = delete;
|
||||
DataPacker& operator=(const DataPacker& other) = delete;
|
||||
|
||||
protected:
|
||||
char* m_data;
|
||||
size_t m_size;
|
||||
size_t m_capacity;
|
||||
size_t m_granularity;
|
||||
size_t m_readPosition;
|
||||
bool m_state;
|
||||
bool m_ownBuffer;
|
||||
};
|
||||
|
||||
#endif // DATAPACKER_H
|
60
src/endian.h
Normal file
60
src/endian.h
Normal file
@ -0,0 +1,60 @@
|
||||
#ifndef ENDIAN_H
|
||||
#define ENDIAN_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC)
|
||||
#ifndef BIG_ENDIAN
|
||||
#define BIG_ENDIAN
|
||||
#endif
|
||||
#else
|
||||
#ifndef LITTLE_ENDIAN
|
||||
#define LITTLE_ENDIAN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
#if (defined(__clang__) && __has_builtin(__builtin_bswap32) && __has_builtin(__builtin_bswap16)) \
|
||||
|| (defined(__GNUC__ ) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)))
|
||||
#define BYTE_SWAP_16(x) __builtin_bswap16(x)
|
||||
#define BYTE_SWAP_32(x) __builtin_bswap32(x)
|
||||
#elif defined(_MSC_VER)
|
||||
#ifdef __cplusplus
|
||||
#include <cstdlib>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#define BYTE_SWAP_16(x) _byteswap_ushort(x)
|
||||
#define BYTE_SWAP_32(x) _byteswap_ulong(x)
|
||||
#else
|
||||
inline uint16_t BYTE_SWAP_16(uint16_t x)
|
||||
{
|
||||
return static_cast<uint16_t>((x << 8) | (x >> 8));
|
||||
}
|
||||
|
||||
inline uint32_t BYTE_SWAP_32(uint32_t x)
|
||||
{
|
||||
return static_cast<uint32_t>((x << 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x >> 24));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef BIG_ENDIAN
|
||||
#define BIG_ENDIAN_WORD(x) (x)
|
||||
#define BIG_ENDIAN_DWORD(x) (x)
|
||||
#define LITTLE_ENDIAN_WORD(x) BYTE_SWAP_16(x)
|
||||
#define LITTLE_ENDIAN_DWORD(x) BYTE_SWAP_32(x)
|
||||
#else // Little endian machine
|
||||
#define BIG_ENDIAN_WORD(x) BYTE_SWAP_16(x)
|
||||
#define BIG_ENDIAN_DWORD(x) BYTE_SWAP_32(x)
|
||||
#define LITTLE_ENDIAN_WORD(x) (x)
|
||||
#define LITTLE_ENDIAN_DWORD(x) (x)
|
||||
#endif // LITTLE_ENDIAN
|
||||
|
||||
#endif // ENDIAN_H
|
331
src/flacfile.cpp
Normal file
331
src/flacfile.cpp
Normal file
@ -0,0 +1,331 @@
|
||||
#include "flacfile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#ifndef UNUSED_ARG
|
||||
#define UNUSED_ARG(x) (void)x
|
||||
#endif
|
||||
|
||||
// FLAC stream read callback
|
||||
FLAC__StreamDecoderReadStatus flac_read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data)
|
||||
{
|
||||
UNUSED_ARG(decoder);
|
||||
|
||||
FlacFile* flacFile = reinterpret_cast<FlacFile*>(client_data);
|
||||
std::ifstream *file = flacFile->m_file;
|
||||
|
||||
if (!file || !file->is_open())
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
||||
|
||||
file->read(reinterpret_cast<char *>(&buffer[0]), *bytes);
|
||||
*bytes = static_cast<size_t>(file->gcount());
|
||||
|
||||
// Fix ifstream stupidity: eof is not a failure condition
|
||||
if (file->fail() && file->eof())
|
||||
file->clear();
|
||||
|
||||
if (file->fail())
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
||||
else if (*bytes == 0)
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
||||
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
// FLAC stream seek callback
|
||||
FLAC__StreamDecoderSeekStatus flac_seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data)
|
||||
{
|
||||
UNUSED_ARG(decoder);
|
||||
|
||||
FlacFile* flacFile = reinterpret_cast<FlacFile*>(client_data);
|
||||
std::ifstream *file = flacFile->m_file;
|
||||
|
||||
if (!file || !file->is_open())
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
|
||||
|
||||
file->seekg(absolute_byte_offset, std::ios::beg);
|
||||
|
||||
if (file->fail())
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
|
||||
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
|
||||
}
|
||||
|
||||
// FLAC stream tell callback
|
||||
FLAC__StreamDecoderTellStatus flac_tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data)
|
||||
{
|
||||
UNUSED_ARG(decoder);
|
||||
|
||||
FlacFile* flacFile = reinterpret_cast<FlacFile*>(client_data);
|
||||
std::ifstream *file = flacFile->m_file;
|
||||
|
||||
if (!file || !file->is_open())
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
|
||||
|
||||
std::ios::pos_type temp = file->tellg();
|
||||
*absolute_byte_offset = static_cast<FLAC__uint64>(temp);
|
||||
if (temp < 0)
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
|
||||
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
||||
}
|
||||
|
||||
// FLAC stream length callback
|
||||
FLAC__StreamDecoderLengthStatus flac_length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data)
|
||||
{
|
||||
UNUSED_ARG(decoder);
|
||||
|
||||
FlacFile* flacFile = reinterpret_cast<FlacFile*>(client_data);
|
||||
std::ifstream *file = flacFile->m_file;
|
||||
|
||||
if (!file || !file->is_open())
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
|
||||
|
||||
std::ios::pos_type position = file->tellg();
|
||||
if (position < 0)
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
|
||||
|
||||
file->seekg(0, std::ios::end);
|
||||
if (file->fail())
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
|
||||
|
||||
std::ios::pos_type temp = file->tellg();
|
||||
*stream_length = static_cast<FLAC__uint64>(temp);
|
||||
if (temp < 0)
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
|
||||
|
||||
file->seekg(position, std::ios::beg);
|
||||
if (file->fail())
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
|
||||
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
|
||||
}
|
||||
|
||||
// FLAC stream end of file callback
|
||||
FLAC__bool flac_eof_cb(const FLAC__StreamDecoder *decoder, void *client_data)
|
||||
{
|
||||
UNUSED_ARG(decoder);
|
||||
|
||||
FlacFile* flacFile = reinterpret_cast<FlacFile*>(client_data);
|
||||
std::ifstream *file = flacFile->m_file;
|
||||
|
||||
if (!file || !file->is_open())
|
||||
return false;
|
||||
|
||||
return file->eof();
|
||||
}
|
||||
|
||||
// FLAC stream write callback
|
||||
FLAC__StreamDecoderWriteStatus flac_write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data)
|
||||
{
|
||||
UNUSED_ARG(decoder);
|
||||
|
||||
FlacFile* flacFile = reinterpret_cast<FlacFile*>(client_data);
|
||||
|
||||
// This should not be called if there bytes still left in the buffer!
|
||||
if (flacFile->bytesAvailable())
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
|
||||
// Get the audio characteristics from the frame header
|
||||
uint32_t samples = frame->header.blocksize;
|
||||
uint32_t channels = frame->header.channels;
|
||||
uint32_t bitsPerSample = frame->header.bits_per_sample;
|
||||
|
||||
// Make sure we have enough room for the samples and reset the read pointer
|
||||
// FLAC seems to pass a constant sample size so it should not trigger too many memory allocations
|
||||
flacFile->m_buffer.resize(samples * 4);
|
||||
flacFile->m_readPosition = 0;
|
||||
|
||||
// Set up the pointers for the conversion, in case of a monaural FLAC we duplicate the channel
|
||||
int16_t* out = reinterpret_cast<int16_t*>(&flacFile->m_buffer[0]);
|
||||
const FLAC__int32* left = buffer[0];
|
||||
const FLAC__int32* right = buffer[channels == 1 ? 0 : 1];
|
||||
|
||||
// Copy the samples in the buffer
|
||||
if (bitsPerSample == 16)
|
||||
{
|
||||
while(samples >= 8)
|
||||
{
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
samples -= 8;
|
||||
}
|
||||
|
||||
while(samples)
|
||||
{
|
||||
*out++ = static_cast<int16_t>(*left++);
|
||||
*out++ = static_cast<int16_t>(*right++);
|
||||
--samples;
|
||||
}
|
||||
}
|
||||
else if (bitsPerSample == 8)
|
||||
{
|
||||
while(samples)
|
||||
{
|
||||
*out++ = static_cast<int16_t>((*left++) << 8);
|
||||
*out++ = static_cast<int16_t>((*right++) << 8);
|
||||
--samples;
|
||||
}
|
||||
}
|
||||
else if (bitsPerSample == 24)
|
||||
{
|
||||
while(samples)
|
||||
{
|
||||
*out++ = static_cast<int16_t>((*left++) >> 8);
|
||||
*out++ = static_cast<int16_t>((*right++) >> 8);
|
||||
--samples;
|
||||
}
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
// FLAC stream error callback
|
||||
void flac_error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
|
||||
{
|
||||
UNUSED_ARG(decoder);
|
||||
UNUSED_ARG(status);
|
||||
UNUSED_ARG(client_data);
|
||||
}
|
||||
|
||||
FlacFile::FlacFile() :
|
||||
m_decoder(nullptr),
|
||||
m_file(nullptr),
|
||||
m_buffer(),
|
||||
m_readPosition(0)
|
||||
{
|
||||
}
|
||||
|
||||
FlacFile::~FlacFile()
|
||||
{
|
||||
if (m_decoder)
|
||||
FLAC__stream_decoder_delete(m_decoder);
|
||||
}
|
||||
|
||||
bool FlacFile::initialize(std::ifstream *file)
|
||||
{
|
||||
// Set the new pointer to the file stream
|
||||
m_file = file;
|
||||
|
||||
// Empty the read buffer
|
||||
m_readPosition = m_buffer.size();
|
||||
|
||||
// Create and initialize the decoder, if needed
|
||||
if (!m_decoder)
|
||||
{
|
||||
m_decoder = FLAC__stream_decoder_new();
|
||||
if (!m_decoder)
|
||||
return false;
|
||||
|
||||
FLAC__StreamDecoderInitStatus result = FLAC__stream_decoder_init_stream(
|
||||
m_decoder,
|
||||
flac_read_cb,
|
||||
flac_seek_cb,
|
||||
flac_tell_cb,
|
||||
flac_length_cb,
|
||||
flac_eof_cb,
|
||||
flac_write_cb,
|
||||
nullptr,
|
||||
flac_error_cb,
|
||||
this);
|
||||
if (result != FLAC__STREAM_DECODER_INIT_STATUS_OK)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the decoder already exists, just reset it
|
||||
return (FLAC__stream_decoder_reset(m_decoder) != 0);
|
||||
}
|
||||
|
||||
size_t FlacFile::bytesAvailable() const
|
||||
{
|
||||
return m_buffer.size() - m_readPosition;
|
||||
}
|
||||
|
||||
size_t FlacFile::read(char *data, size_t size)
|
||||
{
|
||||
size_t done = 0;
|
||||
|
||||
if (!m_decoder)
|
||||
return done;
|
||||
|
||||
// Loop until the requested size has been read
|
||||
while(size)
|
||||
{
|
||||
// Loop processing single frames until some data is available
|
||||
while(!bytesAvailable())
|
||||
{
|
||||
if (!FLAC__stream_decoder_process_single(m_decoder))
|
||||
return done;
|
||||
|
||||
FLAC__StreamDecoderState status = FLAC__stream_decoder_get_state(m_decoder);
|
||||
if ((status == FLAC__STREAM_DECODER_END_OF_STREAM) || (status == FLAC__STREAM_DECODER_ABORTED))
|
||||
return done;
|
||||
}
|
||||
|
||||
// Determine the slice of data to copy
|
||||
size_t slice = std::min(size, bytesAvailable());
|
||||
|
||||
// Copy the data
|
||||
std::memcpy(data, &m_buffer[m_readPosition], slice);
|
||||
|
||||
// Advance the pointers
|
||||
m_readPosition += slice;
|
||||
done += slice;
|
||||
data += slice;
|
||||
size -= slice;
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
bool FlacFile::seek(size_t position)
|
||||
{
|
||||
if (!m_decoder)
|
||||
return false;
|
||||
|
||||
// Empty the read buffer
|
||||
m_readPosition = m_buffer.size();
|
||||
|
||||
// Position is in bytes and FLAC wants samples so we divide by 4 (2 channels, 16 bits)
|
||||
return (FLAC__stream_decoder_seek_absolute(m_decoder, static_cast<FLAC__uint64>(position / 4)) != 0);
|
||||
}
|
||||
|
||||
size_t FlacFile::length()
|
||||
{
|
||||
if (!m_decoder)
|
||||
return false;
|
||||
|
||||
size_t size = static_cast<size_t>(FLAC__stream_decoder_get_total_samples(m_decoder));
|
||||
int i = 1000;
|
||||
|
||||
// If the info is not available, decode stuff until it is
|
||||
while((size == 0) && (i > 0))
|
||||
{
|
||||
FLAC__stream_decoder_process_single(m_decoder);
|
||||
size = static_cast<size_t>(FLAC__stream_decoder_get_total_samples(m_decoder));
|
||||
--i;
|
||||
}
|
||||
|
||||
return size * 4;
|
||||
}
|
||||
|
||||
void FlacFile::cleanup()
|
||||
{
|
||||
}
|
50
src/flacfile.h
Normal file
50
src/flacfile.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef FLACFILE_H
|
||||
#define FLACFILE_H
|
||||
|
||||
#include "FLAC/stream_decoder.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
class FlacFile
|
||||
{
|
||||
public:
|
||||
FlacFile();
|
||||
~FlacFile();
|
||||
|
||||
// Non copyable
|
||||
FlacFile(const FlacFile&) = delete;
|
||||
|
||||
// Non copyable
|
||||
FlacFile& operator=(const FlacFile&) = delete;
|
||||
|
||||
bool initialize(std::ifstream* file);
|
||||
|
||||
size_t bytesAvailable() const;
|
||||
|
||||
size_t read(char *data, size_t size);
|
||||
|
||||
bool seek(size_t position);
|
||||
|
||||
size_t length();
|
||||
|
||||
void cleanup();
|
||||
|
||||
// Declare all callbacks as friends so they can modify the internals
|
||||
friend FLAC__StreamDecoderReadStatus flac_read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data);
|
||||
friend FLAC__StreamDecoderSeekStatus flac_seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data);
|
||||
friend FLAC__StreamDecoderTellStatus flac_tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data);
|
||||
friend FLAC__StreamDecoderLengthStatus flac_length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data);
|
||||
friend FLAC__bool flac_eof_cb(const FLAC__StreamDecoder *decoder, void *client_data);
|
||||
friend FLAC__StreamDecoderWriteStatus flac_write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data);
|
||||
friend void flac_error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
|
||||
|
||||
protected:
|
||||
FLAC__StreamDecoder* m_decoder;
|
||||
std::ifstream* m_file;
|
||||
std::vector<char> m_buffer;
|
||||
size_t m_readPosition;
|
||||
};
|
||||
|
||||
#endif // FLACFILE_H
|
25
src/input.cpp
Normal file
25
src/input.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include "input.h"
|
||||
|
||||
Input::Input() :
|
||||
input1(0xFF),
|
||||
input2(0xFF),
|
||||
input3(0x0F),
|
||||
selector(0)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void Input::reset()
|
||||
{
|
||||
input1 = 0xFF;
|
||||
input2 = 0xFF;
|
||||
input3 = 0x0F;
|
||||
selector = 0;
|
||||
}
|
||||
|
||||
void Input::setInput(uint8_t new1, uint8_t new2, uint8_t new3)
|
||||
{
|
||||
input1 = new1;
|
||||
input2 = new2;
|
||||
input3 = new3 & 0x0F;
|
||||
}
|
36
src/input.h
Normal file
36
src/input.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef INPUT_H
|
||||
#define INPUT_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Input
|
||||
{
|
||||
public:
|
||||
enum ControllerState {
|
||||
Up = 0x01,
|
||||
Down = 0x02,
|
||||
Left = 0x04,
|
||||
Right = 0x08,
|
||||
A = 0x10,
|
||||
B = 0x20,
|
||||
C = 0x40,
|
||||
D = 0x80,
|
||||
Controller1Start = 0x01,
|
||||
Controller1Select = 0x02,
|
||||
Controller2Start = 0x04,
|
||||
Controller2Select = 0x08
|
||||
};
|
||||
|
||||
Input();
|
||||
|
||||
void reset();
|
||||
|
||||
void setInput(uint8_t new1, uint8_t new2, uint8_t new3);
|
||||
|
||||
uint8_t input1;
|
||||
uint8_t input2;
|
||||
uint8_t input3;
|
||||
uint8_t selector;
|
||||
};
|
||||
|
||||
#endif // INPUT_H
|
799
src/lc8951.cpp
Normal file
799
src/lc8951.cpp
Normal file
@ -0,0 +1,799 @@
|
||||
#include "lc8951.h"
|
||||
#include "neogeocd.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
LC8951::LC8951() :
|
||||
status(CdIdle),
|
||||
registerPointer(0),
|
||||
commandPointer(0),
|
||||
responsePointer(0),
|
||||
strobe(0),
|
||||
SBOUT(0),
|
||||
IFCTRL(0),
|
||||
DBCL(0),
|
||||
DBCH(0),
|
||||
DACL(0),
|
||||
DACH(0),
|
||||
DTRG(0),
|
||||
DTACK(0),
|
||||
WAL(0),
|
||||
WAH(0),
|
||||
CTRL0(0),
|
||||
CTRL1(0),
|
||||
PTL(0),
|
||||
PTH(0),
|
||||
COMIN(0),
|
||||
IFSTAT(0),
|
||||
HEAD0(0),
|
||||
HEAD1(0),
|
||||
HEAD2(0),
|
||||
HEAD3(0),
|
||||
STAT0(0),
|
||||
STAT1(0),
|
||||
STAT2(0),
|
||||
STAT3(0)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void LC8951::reset()
|
||||
{
|
||||
status = CdIdle;
|
||||
|
||||
std::memset(commandPacket, 0, sizeof(commandPacket));
|
||||
std::memset(responsePacket, 0, sizeof(responsePacket));
|
||||
setPacketChecksum(responsePacket);
|
||||
|
||||
registerPointer = 0;
|
||||
resetPacketPointers();
|
||||
|
||||
updateHeadRegisters(0);
|
||||
|
||||
SBOUT = 0;
|
||||
IFCTRL = 0;
|
||||
DBCL = 0;
|
||||
DBCH = 0;
|
||||
DACL = 0;
|
||||
DACH = 0;
|
||||
DTRG = 0;
|
||||
DTACK = 0;
|
||||
WAL = 0x30; // 2352
|
||||
WAH = 0x09;
|
||||
CTRL0 = 0;
|
||||
CTRL1 = 0;
|
||||
PTL = 0;
|
||||
PTH = 0;
|
||||
COMIN = 0;
|
||||
IFSTAT = 0xFF;
|
||||
STAT0 = 0;
|
||||
STAT1 = 0;
|
||||
STAT2 = 0;
|
||||
STAT3 = 0;
|
||||
|
||||
std::memset(buffer, 0, sizeof(buffer));
|
||||
}
|
||||
|
||||
void LC8951::updateHeadRegisters(uint32_t lba)
|
||||
{
|
||||
uint8_t m, s, f;
|
||||
|
||||
CDROM_LOG(LOG_INFO, "CD LBA = %d\n", lba);
|
||||
|
||||
Cdrom::toMSF(Cdrom::fromLBA(lba), m, s, f);
|
||||
|
||||
HEAD0 = Cdrom::toBCD(m);
|
||||
HEAD1 = Cdrom::toBCD(s);
|
||||
HEAD2 = Cdrom::toBCD(f);
|
||||
HEAD3 = 0x01;
|
||||
}
|
||||
|
||||
uint8_t LC8951::readRegister()
|
||||
{
|
||||
uint8_t value;
|
||||
|
||||
switch (registerPointer & 0xF)
|
||||
{
|
||||
case 0x00: // COMIN
|
||||
value = COMIN;
|
||||
CDROM_LOG(LOG_INFO, "READ COMIN = %02X\n", value);
|
||||
break;
|
||||
case 0x01: // IFSTAT
|
||||
value = IFSTAT;
|
||||
CDROM_LOG(LOG_INFO, "READ IFSTAT = %02X\n", value);
|
||||
break;
|
||||
case 0x02: // DBCL
|
||||
value = DBCL;
|
||||
CDROM_LOG(LOG_INFO, "READ DBCL = %02X\n", value);
|
||||
break;
|
||||
case 0x03: // DCBH
|
||||
value = DBCH;
|
||||
CDROM_LOG(LOG_INFO, "READ DBCH = %02X\n", value);
|
||||
break;
|
||||
case 0x04: // HEAD0
|
||||
value = (CTRL1 & SHDREN) ? 0 : HEAD0;
|
||||
CDROM_LOG(LOG_INFO, "READ HEAD0 = %02X\n", value);
|
||||
break;
|
||||
case 0x05: // HEAD1
|
||||
value = (CTRL1 & SHDREN) ? 0 : HEAD1;
|
||||
CDROM_LOG(LOG_INFO, "READ HEAD1 = %02X\n", value);
|
||||
break;
|
||||
case 0x06: // HEAD2
|
||||
value = (CTRL1 & SHDREN) ? 0 : HEAD2;
|
||||
CDROM_LOG(LOG_INFO, "READ HEAD2 = %02X\n", value);
|
||||
break;
|
||||
case 0x07: // HEAD3
|
||||
value = (CTRL1 & SHDREN) ? 0 : HEAD3;
|
||||
CDROM_LOG(LOG_INFO, "READ HEAD3 = %02X\n", value);
|
||||
break;
|
||||
case 0x08: // PTL
|
||||
value = PTL;
|
||||
CDROM_LOG(LOG_INFO, "READ PTL = %02X\n", value);
|
||||
break;
|
||||
case 0x09: // PTH
|
||||
value = PTH;
|
||||
CDROM_LOG(LOG_INFO, "READ PTH = %02X\n", value);
|
||||
break;
|
||||
case 0x0A: // WAL
|
||||
value = WAL;
|
||||
CDROM_LOG(LOG_INFO, "READ WAL = %02X\n", value);
|
||||
break;
|
||||
case 0x0B: // WAH
|
||||
value = WAH;
|
||||
CDROM_LOG(LOG_INFO, "READ WAH = %02X\n", value);
|
||||
break;
|
||||
case 0x0C: // STAT0
|
||||
value = STAT0;
|
||||
CDROM_LOG(LOG_INFO, "READ STAT0 = %02X\n", value);
|
||||
break;
|
||||
case 0x0D: // STAT1
|
||||
value = STAT1;
|
||||
CDROM_LOG(LOG_INFO, "READ STAT1 = %02X\n", value);
|
||||
break;
|
||||
case 0x0E: // STAT2
|
||||
value = STAT2;
|
||||
CDROM_LOG(LOG_INFO, "READ STAT2 = %02X\n", value);
|
||||
break;
|
||||
case 0x0F: // STAT3
|
||||
value = STAT3;
|
||||
CDROM_LOG(LOG_INFO, "READ STAT3 = %02X\n", value);
|
||||
|
||||
// Reading STAT3 clears DECI
|
||||
IFSTAT = IFSTAT & ~DECI;
|
||||
break;
|
||||
}
|
||||
|
||||
incrementRegisterPointer();
|
||||
return value;
|
||||
}
|
||||
|
||||
void LC8951::writeRegister(uint8_t data)
|
||||
{
|
||||
switch (registerPointer & 0xF)
|
||||
{
|
||||
case 0x00:
|
||||
CDROM_LOG(LOG_INFO, "WRITE SBOUT = %02X\n", data);
|
||||
SBOUT = data;
|
||||
break;
|
||||
case 0x01:
|
||||
CDROM_LOG(LOG_INFO, "WRITE IFCTRL = %02X\n", data);
|
||||
IFCTRL = data;
|
||||
break;
|
||||
case 0x02:
|
||||
CDROM_LOG(LOG_INFO, "WRITE DBCL = %02X\n", data);
|
||||
DBCL = data;
|
||||
break;
|
||||
case 0x03:
|
||||
CDROM_LOG(LOG_INFO, "WRITE DBCH = %02X\n", data);
|
||||
DBCH = data;
|
||||
break;
|
||||
case 0x04:
|
||||
CDROM_LOG(LOG_INFO, "WRITE DACL = %02X\n", data);
|
||||
DACL = data;
|
||||
break;
|
||||
case 0x05:
|
||||
CDROM_LOG(LOG_INFO, "WRITE DACH = %02X\n", data);
|
||||
DACH = data;
|
||||
break;
|
||||
case 0x06:
|
||||
CDROM_LOG(LOG_INFO, "WRITE DTRG = %02X\n", data);
|
||||
DTRG = data;
|
||||
|
||||
// Writing DTRG sets !DTBSY if data output is enabled
|
||||
if (IFCTRL & DOUTEN)
|
||||
IFSTAT &= ~DTBSY;
|
||||
break;
|
||||
case 0x07:
|
||||
CDROM_LOG(LOG_INFO, "WRITE DTACK = %02X\n", data);
|
||||
DTACK = data;
|
||||
|
||||
// Writing DTACK clears !DTEI
|
||||
IFSTAT = IFSTAT | DTEI;
|
||||
break;
|
||||
case 0x08:
|
||||
CDROM_LOG(LOG_INFO, "WRITE WAL = %02X\n", data);
|
||||
WAL = data;
|
||||
break;
|
||||
case 0x09:
|
||||
CDROM_LOG(LOG_INFO, "WRITE WAH = %02X\n", data);
|
||||
WAH = data;
|
||||
break;
|
||||
case 0x0A: // CTRL0
|
||||
CDROM_LOG(LOG_INFO, "WRITE CTRL0 = %02X\n", data);
|
||||
CTRL0 = data;
|
||||
break;
|
||||
case 0x0B:
|
||||
CDROM_LOG(LOG_INFO, "WRITE CTRL1 = %02X\n", data);
|
||||
CTRL1 = data;
|
||||
break;
|
||||
case 0x0C:
|
||||
CDROM_LOG(LOG_INFO, "WRITE PTL = %02X\n", data);
|
||||
PTL = data;
|
||||
break;
|
||||
case 0x0D:
|
||||
CDROM_LOG(LOG_INFO, "WRITE PTH = %02X\n", data);
|
||||
PTH = data;
|
||||
break;
|
||||
case 0x0E: // CTRL2
|
||||
CDROM_LOG(LOG_INFO, "WRITE CTRL2 = %02X\n", data);
|
||||
break;
|
||||
case 0x0F: // RESET
|
||||
CDROM_LOG(LOG_INFO, "LC8951 RESET = %02X\n", data);
|
||||
break;
|
||||
}
|
||||
|
||||
incrementRegisterPointer();
|
||||
}
|
||||
|
||||
void LC8951::incrementRegisterPointer()
|
||||
{
|
||||
if (!registerPointer)
|
||||
return;
|
||||
|
||||
registerPointer = (registerPointer & 0xF0) | ((registerPointer + 1) & 0x0F);
|
||||
}
|
||||
|
||||
uint8_t LC8951::readResponsePacket()
|
||||
{
|
||||
if (responsePointer & 1)
|
||||
return ((responsePacket[responsePointer >> 1] & 0x0F) | (strobe << 4));
|
||||
|
||||
return ((responsePacket[responsePointer >> 1] >> 4) | (strobe << 4));
|
||||
}
|
||||
|
||||
void LC8951::writeCommandPacket(uint8_t data)
|
||||
{
|
||||
if (commandPointer & 1)
|
||||
commandPacket[commandPointer >> 1] = (commandPacket[commandPointer >> 1] & 0xF0) | (data & 0x0F);
|
||||
else
|
||||
commandPacket[commandPointer >> 1] = (commandPacket[commandPointer >> 1] & 0x0F) | (data << 4);
|
||||
}
|
||||
|
||||
void LC8951::increasePacketPointer(uint8_t data)
|
||||
{
|
||||
switch (data)
|
||||
{
|
||||
case 0x00:
|
||||
break;
|
||||
case 0x01:
|
||||
commandPointer = (commandPointer + 1) % 10;
|
||||
if (!commandPointer)
|
||||
processCdCommand();
|
||||
break;
|
||||
case 0x02:
|
||||
strobe = 0;
|
||||
responsePointer = (responsePointer + 1) % 10;
|
||||
break;
|
||||
case 0x03:
|
||||
strobe = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LC8951::resetPacketPointers()
|
||||
{
|
||||
commandPointer = 0;
|
||||
responsePointer = 9;
|
||||
strobe = 1;
|
||||
}
|
||||
|
||||
void LC8951::setRegisterPointer(uint8_t value)
|
||||
{
|
||||
registerPointer = value;
|
||||
}
|
||||
|
||||
uint8_t LC8951::calculatePacketChecksum(const uint8_t* packet)
|
||||
{
|
||||
uint8_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
checksum += (packet[i] >> 4);
|
||||
checksum += (packet[i] & 0x0F);
|
||||
}
|
||||
|
||||
checksum += (packet[4] >> 4);
|
||||
checksum += 5;
|
||||
checksum = (~checksum) & 0x0F;
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
void LC8951::setPacketChecksum(uint8_t* packet)
|
||||
{
|
||||
uint8_t checksum = calculatePacketChecksum(packet);
|
||||
packet[4] = (packet[4] & 0xF0) | checksum;
|
||||
}
|
||||
|
||||
// Not really done by the LC8951 but by the CD-ROM board behind it
|
||||
void LC8951::processCdCommand()
|
||||
{
|
||||
if (commandPacket[0])
|
||||
{
|
||||
CDROM_LOG(LOG_INFO, "COMMAND %02X%02X%02X%02X%02X\n",
|
||||
commandPacket[0],
|
||||
commandPacket[1],
|
||||
commandPacket[2],
|
||||
commandPacket[3],
|
||||
commandPacket[4]);
|
||||
}
|
||||
|
||||
if ((commandPacket[4] & 0x0F) != calculatePacketChecksum(commandPacket))
|
||||
{
|
||||
CDROM_LOG(LOG_INFO, "CD command with wrong checksum!");
|
||||
responsePacket[0] = status;
|
||||
responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
setPacketChecksum(responsePacket);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (commandPacket[0])
|
||||
{
|
||||
case 0x00: // Status
|
||||
responsePacket[0] = (responsePacket[0] & 0x0F) | status;
|
||||
break;
|
||||
|
||||
case 0x10: // Stop
|
||||
neocd.cdrom.stop();
|
||||
status = CdIdle;
|
||||
responsePacket[0] = status;
|
||||
responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
break;
|
||||
|
||||
case 0x20: // Query info
|
||||
if ((status == CdIdle) && (!neocd.cdrom.isTocEmpty()))
|
||||
status = CdStopped;
|
||||
|
||||
switch (commandPacket[1] & 0x0F) // Sub command
|
||||
{
|
||||
case 0x00: // Get current position
|
||||
{
|
||||
uint8_t m, s, f;
|
||||
Cdrom::toMSF(Cdrom::fromLBA(neocd.cdrom.position()), m, s, f);
|
||||
responsePacket[0] = status;
|
||||
responsePacket[1] = Cdrom::toBCD(m);
|
||||
responsePacket[2] = Cdrom::toBCD(s);
|
||||
responsePacket[3] = Cdrom::toBCD(f);
|
||||
responsePacket[4] = neocd.cdrom.isData() ? 0x40 : 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x01: // Get current position (relative)
|
||||
{
|
||||
uint32_t position;
|
||||
|
||||
if (neocd.cdrom.isPregap()) // Pregaps are counted down
|
||||
position = (neocd.cdrom.currentTrackPosition() + neocd.cdrom.currentIndexSize()) - (neocd.cdrom.position() + 1);
|
||||
else
|
||||
position = neocd.cdrom.position() - neocd.cdrom.currentTrackPosition();
|
||||
|
||||
uint8_t m, s, f;
|
||||
Cdrom::toMSF(position, m, s, f);
|
||||
|
||||
responsePacket[0] = status | 0x01;
|
||||
responsePacket[1] = Cdrom::toBCD(m);
|
||||
responsePacket[2] = Cdrom::toBCD(s);
|
||||
responsePacket[3] = Cdrom::toBCD(f);
|
||||
responsePacket[4] = neocd.cdrom.isData() ? 0x40 : 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02: // Get current track
|
||||
{
|
||||
/*
|
||||
NOTE: Copy protection can be simulated by generating indexes for track 01 as follow:
|
||||
index = std::min(99, (((m * 100) + s) / 4) + 1);
|
||||
*/
|
||||
TrackIndex trackIndex = neocd.cdrom.currentTrackIndex();
|
||||
responsePacket[0] = status | 0x02;
|
||||
responsePacket[1] = Cdrom::toBCD(trackIndex.track());
|
||||
responsePacket[2] = Cdrom::toBCD(trackIndex.index());
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = neocd.cdrom.isData() ? 0x40 : 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x03: // Get leadout address (msf)
|
||||
{
|
||||
uint8_t m, s, f;
|
||||
Cdrom::toMSF(Cdrom::fromLBA(neocd.cdrom.leadout()), m, s, f);
|
||||
responsePacket[0] = status | 0x03;
|
||||
responsePacket[1] = Cdrom::toBCD(m);
|
||||
responsePacket[2] = Cdrom::toBCD(s);
|
||||
responsePacket[3] = Cdrom::toBCD(f);
|
||||
responsePacket[4] = 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x04: // Get first track and last track
|
||||
{
|
||||
responsePacket[0] = status | 0x04;
|
||||
responsePacket[1] = Cdrom::toBCD(neocd.cdrom.firstTrack());
|
||||
responsePacket[2] = Cdrom::toBCD(neocd.cdrom.lastTrack());
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x05: // Get track info
|
||||
{
|
||||
uint8_t track = Cdrom::fromBCD(commandPacket[2]);
|
||||
uint32_t position = Cdrom::fromLBA(neocd.cdrom.trackPosition(track));
|
||||
uint8_t m, s, f;
|
||||
Cdrom::toMSF(position, m, s, f);
|
||||
|
||||
responsePacket[0] = status | 0x05;
|
||||
responsePacket[1] = Cdrom::toBCD(m);
|
||||
responsePacket[2] = Cdrom::toBCD(s);
|
||||
|
||||
if (neocd.cdrom.trackIsData(track))
|
||||
responsePacket[3] = Cdrom::toBCD(f) | 0x80;
|
||||
else
|
||||
responsePacket[3] = Cdrom::toBCD(f);
|
||||
|
||||
responsePacket[4] = commandPacket[2] << 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x06: // Check end of disc?
|
||||
{
|
||||
if (neocd.cdrom.position() >= neocd.cdrom.leadout())
|
||||
status = CdEndOfDisc;
|
||||
|
||||
responsePacket[0] = status | 0x06;
|
||||
responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = neocd.cdrom.isData() ? 0x40 : 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x07: // Unknown, CDZ copy protection related. (Disc recognition)
|
||||
{
|
||||
responsePacket[0] = status | 0x07;
|
||||
responsePacket[1] = 0x02; // Possible values: 2, 5, E, F. Most games want 2, Twinkle Star Sprites want F
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
CDROM_LOG(LOG_INFO, "CDROM: Received unknown 0x20 sub-command (%02X%02X%02X%02X%02X)\n",
|
||||
commandPacket[0],
|
||||
commandPacket[1],
|
||||
commandPacket[2],
|
||||
commandPacket[3],
|
||||
commandPacket[4]);
|
||||
responsePacket[0] = status;
|
||||
responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x30: // Play
|
||||
{
|
||||
uint8_t m, s, f;
|
||||
|
||||
m = Cdrom::fromBCD(commandPacket[1]);
|
||||
s = Cdrom::fromBCD(commandPacket[2]);
|
||||
f = Cdrom::fromBCD(commandPacket[3]);
|
||||
|
||||
uint32_t position = Cdrom::toLBA(Cdrom::fromMSF(m, s, f));
|
||||
|
||||
/*
|
||||
CDZ Bios sometimes send the play command twice.
|
||||
There is a command FIFO at $7472(A5)
|
||||
Read pointer is $7650(A5) and write pointer $7651(A5)
|
||||
The code at $C0B4B2 writes 7 (Send play command) to the FIFO and is systematically called twice.
|
||||
Some tracks use this, other tracks use $C0B3C6 (called once)
|
||||
Is this a bug in the BIOS? Or is something not properly emulated?
|
||||
*/
|
||||
neocd.cdrom.play();
|
||||
neocd.cdrom.seek(position);
|
||||
// updateHeadRegisters(neocd.cdrom.position());
|
||||
|
||||
status = CdPlaying;
|
||||
responsePacket[0] = status | 0x02;
|
||||
responsePacket[1] = Cdrom::toBCD(neocd.cdrom.currentTrackIndex().track());
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x40: // Seek
|
||||
CDROM_LOG(LOG_INFO, "CDROM: Received seek command, ignored (%02X%02X%02X%02X%02X)\n",
|
||||
commandPacket[0],
|
||||
commandPacket[1],
|
||||
commandPacket[2],
|
||||
commandPacket[3],
|
||||
commandPacket[4]);
|
||||
|
||||
neocd.cdrom.stop();
|
||||
status = CdPaused;
|
||||
responsePacket[0] = CdSeeking;
|
||||
responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
break;
|
||||
|
||||
case 0x50: // Unknown, CDZ only
|
||||
responsePacket[0] = status;
|
||||
/* responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;*/
|
||||
break;
|
||||
|
||||
case 0x60: // Pause
|
||||
neocd.cdrom.stop();
|
||||
|
||||
status = CdPaused;
|
||||
responsePacket[0] = status;
|
||||
/* responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;*/
|
||||
break;
|
||||
|
||||
case 0x70: // Resume
|
||||
neocd.cdrom.play();
|
||||
|
||||
status = CdPlaying;
|
||||
responsePacket[0] = status;
|
||||
/* responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;*/
|
||||
break;
|
||||
|
||||
case 0x80: // Scan forward
|
||||
{
|
||||
uint32_t position = neocd.cdrom.position();
|
||||
|
||||
position = std::min(position + SCAN_SPEED, neocd.cdrom.leadout() - 1);
|
||||
|
||||
neocd.cdrom.seek(position);
|
||||
// updateHeadRegisters(neocd.cdrom.position());
|
||||
|
||||
status = CdPlaying;
|
||||
responsePacket[0] = CdScanning;
|
||||
/* responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;*/
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x90: // Scan backward
|
||||
{
|
||||
uint32_t position = neocd.cdrom.position();
|
||||
|
||||
if (position < SCAN_SPEED)
|
||||
position = 0;
|
||||
else
|
||||
position -= SCAN_SPEED;
|
||||
|
||||
neocd.cdrom.seek(position);
|
||||
// updateHeadRegisters(neocd.cdrom.position());
|
||||
|
||||
status = CdPlaying;
|
||||
responsePacket[0] = CdScanning;
|
||||
/* responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;*/
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xB0: // Move to track
|
||||
{
|
||||
uint8_t track = Cdrom::fromBCD(commandPacket[1]);
|
||||
uint32_t position = neocd.cdrom.trackPosition(track);
|
||||
|
||||
neocd.cdrom.play();
|
||||
neocd.cdrom.seek(position);
|
||||
// updateHeadRegisters(neocd.cdrom.position());
|
||||
|
||||
status = CdPlaying;
|
||||
responsePacket[0] = status | 0x02;
|
||||
responsePacket[1] = Cdrom::toBCD(neocd.cdrom.currentTrackIndex().track());
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02: // More CD Protection Stuff?
|
||||
case 0x13: // The results of those are written to $778C(A5)
|
||||
case 0x23: // Then transferred to $800076 which is in the backup RAM... What for?
|
||||
case 0x33:
|
||||
case 0x43:
|
||||
case 0x53:
|
||||
case 0x63:
|
||||
case 0xE2:
|
||||
responsePacket[0] = status;
|
||||
responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* CDROM_LOG(LOG_INFO, "COMMAND %02X%02X%02X%02X%02X\n",
|
||||
commandPacket[0],
|
||||
commandPacket[1],
|
||||
commandPacket[2],
|
||||
commandPacket[3],
|
||||
commandPacket[4]);*/
|
||||
responsePacket[0] = status;
|
||||
responsePacket[1] = 0x00;
|
||||
responsePacket[2] = 0x00;
|
||||
responsePacket[3] = 0x00;
|
||||
responsePacket[4] = 0x00;
|
||||
break;
|
||||
}
|
||||
|
||||
setPacketChecksum(responsePacket);
|
||||
|
||||
/* CDROM_LOG(LOG_INFO, "ANSWER %02X%02X%02X%02X%02X\n",
|
||||
responsePacket[0],
|
||||
responsePacket[1],
|
||||
responsePacket[2],
|
||||
responsePacket[3],
|
||||
responsePacket[4]);*/
|
||||
}
|
||||
|
||||
void LC8951::sectorDecoded()
|
||||
{
|
||||
// If the decoder is not enabled, nothing to do
|
||||
if (!(CTRL0 & DECEN))
|
||||
return;
|
||||
|
||||
// Update the head registers
|
||||
updateHeadRegisters(neocd.cdrom.position());
|
||||
|
||||
// If the current track is not a data track, nothing more to do
|
||||
if (!neocd.cdrom.isData())
|
||||
return;
|
||||
|
||||
// Read the sector in buffer
|
||||
// The Neo Geo CD never change the write address (WA) or pointer registers (PT)
|
||||
// It simply read PT and set DAC to PT + 4 (to skip the header) and DBC to 0x7FF
|
||||
// This means we only need keep the last decoded sector
|
||||
neocd.cdrom.readData(reinterpret_cast<char*>(buffer));
|
||||
|
||||
// Autoincrement WA and PT
|
||||
addWordRegister(WAL, WAH, 2352);
|
||||
addWordRegister(PTL, PTH, 2352);
|
||||
|
||||
STAT0 = CRCOK;
|
||||
STAT1 = 0;
|
||||
|
||||
if (CTRL0 & AUTORQ)
|
||||
STAT2 = CTRL1 & MODRQ;
|
||||
else
|
||||
STAT2 = CTRL1 & (MODRQ | FORMRQ);
|
||||
|
||||
STAT3 = 0;
|
||||
|
||||
// Set !DECI
|
||||
IFSTAT &= ~DECI;
|
||||
}
|
||||
|
||||
void LC8951::endTransfer()
|
||||
{
|
||||
IFSTAT |= DTBSY;
|
||||
|
||||
addWordRegister(DACL, DACH, wordRegister(DBCL, DBCH) + 1);
|
||||
setWordRegister(DBCL, DBCH, 0);
|
||||
}
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const LC8951& lc8951)
|
||||
{
|
||||
out << lc8951.status;
|
||||
out << lc8951.registerPointer;
|
||||
out << lc8951.commandPacket;
|
||||
out << lc8951.commandPointer;
|
||||
out << lc8951.responsePacket;
|
||||
out << lc8951.responsePointer;
|
||||
out << lc8951.strobe;
|
||||
out << lc8951.SBOUT;
|
||||
out << lc8951.IFCTRL;
|
||||
out << lc8951.DBCL;
|
||||
out << lc8951.DBCH;
|
||||
out << lc8951.DACL;
|
||||
out << lc8951.DACH;
|
||||
out << lc8951.DTRG;
|
||||
out << lc8951.DTACK;
|
||||
out << lc8951.WAL;
|
||||
out << lc8951.WAH;
|
||||
out << lc8951.CTRL0;
|
||||
out << lc8951.CTRL1;
|
||||
out << lc8951.PTL;
|
||||
out << lc8951.PTH;
|
||||
out << lc8951.COMIN;
|
||||
out << lc8951.IFSTAT;
|
||||
out << lc8951.HEAD0;
|
||||
out << lc8951.HEAD1;
|
||||
out << lc8951.HEAD2;
|
||||
out << lc8951.HEAD3;
|
||||
out << lc8951.STAT0;
|
||||
out << lc8951.STAT1;
|
||||
out << lc8951.STAT2;
|
||||
out << lc8951.STAT3;
|
||||
out << lc8951.buffer;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
DataPacker& operator>>(DataPacker& in, LC8951& lc8951)
|
||||
{
|
||||
in >> lc8951.status;
|
||||
in >> lc8951.registerPointer;
|
||||
in >> lc8951.commandPacket;
|
||||
in >> lc8951.commandPointer;
|
||||
in >> lc8951.responsePacket;
|
||||
in >> lc8951.responsePointer;
|
||||
in >> lc8951.strobe;
|
||||
in >> lc8951.SBOUT;
|
||||
in >> lc8951.IFCTRL;
|
||||
in >> lc8951.DBCL;
|
||||
in >> lc8951.DBCH;
|
||||
in >> lc8951.DACL;
|
||||
in >> lc8951.DACH;
|
||||
in >> lc8951.DTRG;
|
||||
in >> lc8951.DTACK;
|
||||
in >> lc8951.WAL;
|
||||
in >> lc8951.WAH;
|
||||
in >> lc8951.CTRL0;
|
||||
in >> lc8951.CTRL1;
|
||||
in >> lc8951.PTL;
|
||||
in >> lc8951.PTH;
|
||||
in >> lc8951.COMIN;
|
||||
in >> lc8951.IFSTAT;
|
||||
in >> lc8951.HEAD0;
|
||||
in >> lc8951.HEAD1;
|
||||
in >> lc8951.HEAD2;
|
||||
in >> lc8951.HEAD3;
|
||||
in >> lc8951.STAT0;
|
||||
in >> lc8951.STAT1;
|
||||
in >> lc8951.STAT2;
|
||||
in >> lc8951.STAT3;
|
||||
in >> lc8951.buffer;
|
||||
|
||||
return in;
|
||||
}
|
167
src/lc8951.h
Normal file
167
src/lc8951.h
Normal file
@ -0,0 +1,167 @@
|
||||
#ifndef LC8951_H
|
||||
#define LC8951_H
|
||||
|
||||
//#define CDROM_LOG(x, ...) LOG(x, __VA_ARGS__)
|
||||
#define CDROM_LOG(x, ...)
|
||||
|
||||
#include "datapacker.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class LC8951
|
||||
{
|
||||
public:
|
||||
enum Status
|
||||
{
|
||||
CdIdle = 0x00,
|
||||
CdPlaying = 0x10,
|
||||
CdSeeking = 0x20,
|
||||
CdScanning = 0x30,
|
||||
CdPaused = 0x40,
|
||||
CdStopped = 0x90,
|
||||
CdEndOfDisc = 0xC0
|
||||
};
|
||||
|
||||
enum IFCTRL_BITS
|
||||
{
|
||||
CMDIEN = 0x80,
|
||||
DTEIEN = 0x40,
|
||||
DECIEN = 0x20,
|
||||
CMDBK = 0x10,
|
||||
DTWAI = 0x08,
|
||||
STWAI = 0x04,
|
||||
DOUTEN = 0x02,
|
||||
SOUTEN = 0x01
|
||||
};
|
||||
|
||||
enum IFSTAT_BITS
|
||||
{
|
||||
CMDI = 0x80,
|
||||
DTEI = 0x40,
|
||||
DECI = 0x20,
|
||||
SUBI = 0x10,
|
||||
DTBSY = 0x08,
|
||||
STBSY = 0x04,
|
||||
DTEN = 0x02,
|
||||
STEN = 0x01
|
||||
};
|
||||
|
||||
enum CTRL0_BITS
|
||||
{
|
||||
DECEN = 0x80,
|
||||
LOOKAHEAD = 0x40,
|
||||
E01RQ = 0x20,
|
||||
AUTORQ = 0x10,
|
||||
ERAMRQ = 0x08,
|
||||
WRRQ = 0x04,
|
||||
ECCRQ = 0x02,
|
||||
ENCODE = 0x01
|
||||
};
|
||||
|
||||
enum CTRL1_BITS
|
||||
{
|
||||
SYIEN = 0x80,
|
||||
SYDEN = 0x40,
|
||||
DSCREN = 0x20,
|
||||
COWREN = 0x10,
|
||||
MODRQ = 0x08,
|
||||
FORMRQ = 0x04,
|
||||
MBCKRQ = 0x02,
|
||||
SHDREN = 0x01
|
||||
};
|
||||
|
||||
enum STAT0_BITS
|
||||
{
|
||||
CRCOK = 0x80
|
||||
};
|
||||
|
||||
static const uint32_t SCAN_SPEED = 30;
|
||||
|
||||
LC8951();
|
||||
|
||||
void reset();
|
||||
|
||||
void updateHeadRegisters(uint32_t lba);
|
||||
|
||||
uint8_t readRegister();
|
||||
void writeRegister(uint8_t data);
|
||||
void incrementRegisterPointer();
|
||||
void setRegisterPointer(uint8_t value);
|
||||
|
||||
uint8_t readResponsePacket();
|
||||
void writeCommandPacket(uint8_t data);
|
||||
void increasePacketPointer(uint8_t data);
|
||||
void resetPacketPointers();
|
||||
|
||||
void processCdCommand();
|
||||
|
||||
inline uint16_t wordRegister(const uint8_t& low, const uint8_t& high) const
|
||||
{
|
||||
return (static_cast<uint16_t>(high) << 8) | static_cast<uint16_t>(low);
|
||||
}
|
||||
|
||||
inline void setWordRegister(uint8_t& low, uint8_t& high, const uint16_t& value)
|
||||
{
|
||||
high = value >> 8;
|
||||
low = value;
|
||||
}
|
||||
|
||||
inline void addWordRegister(uint8_t& low, uint8_t& high, uint16_t value)
|
||||
{
|
||||
value += wordRegister(low, high);
|
||||
setWordRegister(low, high, value);
|
||||
}
|
||||
|
||||
void sectorDecoded();
|
||||
|
||||
void endTransfer();
|
||||
|
||||
static uint8_t calculatePacketChecksum(const uint8_t* packet);
|
||||
static void setPacketChecksum(uint8_t* packet);
|
||||
|
||||
friend DataPacker& operator<<(DataPacker& out, const LC8951& lc8951);
|
||||
friend DataPacker& operator>>(DataPacker& in, LC8951& lc8951);
|
||||
|
||||
// Variables to save in savestate
|
||||
uint8_t status;
|
||||
uint32_t registerPointer;
|
||||
uint8_t commandPacket[5];
|
||||
uint32_t commandPointer;
|
||||
uint8_t responsePacket[5];
|
||||
uint32_t responsePointer;
|
||||
uint32_t strobe;
|
||||
|
||||
uint8_t SBOUT; // Status Byte Output
|
||||
uint8_t IFCTRL; // Interface Control ?
|
||||
uint8_t DBCL; // Data Byte Count Low
|
||||
uint8_t DBCH; // Data Byte Count High
|
||||
uint8_t DACL; // Data Address Counter Low
|
||||
uint8_t DACH; // Data Address Counter High
|
||||
uint8_t DTRG; // Data Transfer Trigger
|
||||
uint8_t DTACK; // Data Transfer Acknowledge
|
||||
uint8_t WAL; // Write Address Low
|
||||
uint8_t WAH; // Write Address High
|
||||
uint8_t CTRL0; // Control 0
|
||||
uint8_t CTRL1; // Control 1
|
||||
uint8_t PTL; // Pointer Low
|
||||
uint8_t PTH; // Pointer High
|
||||
|
||||
uint8_t COMIN; // Command Input Register
|
||||
uint8_t IFSTAT; // Interface Status ?
|
||||
uint8_t HEAD0; // Minutes
|
||||
uint8_t HEAD1; // Seconds
|
||||
uint8_t HEAD2; // Frames
|
||||
uint8_t HEAD3; // Mode
|
||||
uint8_t STAT0; // Status 0
|
||||
uint8_t STAT1; // Status 1
|
||||
uint8_t STAT2; // Status 2
|
||||
uint8_t STAT3; // Status 3
|
||||
|
||||
uint8_t buffer[2048];
|
||||
// End variables to save in savestate
|
||||
};
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const LC8951& lc8951);
|
||||
DataPacker& operator>>(DataPacker& in, LC8951& lc8951);
|
||||
|
||||
#endif // LC8951_H
|
708
src/libretro.cpp
Normal file
708
src/libretro.cpp
Normal file
@ -0,0 +1,708 @@
|
||||
#include "neogeocd.h"
|
||||
#include "timeprofiler.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
class BiosListEntry
|
||||
{
|
||||
public:
|
||||
BiosListEntry(const std::string& f, const std::string& d, NeoGeoCD::BiosType t) :
|
||||
filename(f),
|
||||
description(d),
|
||||
type(t)
|
||||
{ }
|
||||
|
||||
std::string filename;
|
||||
std::string description;
|
||||
NeoGeoCD::BiosType type;
|
||||
};
|
||||
|
||||
static const char* BIOS_F_FILENAME = "neocd_f.rom";
|
||||
static const char* BIOS_F_DESCRIPTION = "Front Loader";
|
||||
static const char* BIOS_SF_FILENAME = "neocd_sf.rom";
|
||||
static const char* BIOS_SF_DESCRIPTION = "Front Loader (SMKDAN)";
|
||||
static const char* BIOS_T_FILENAME = "neocd_t.rom";
|
||||
static const char* BIOS_T_DESCRIPTION = "Top Loader";
|
||||
static const char* BIOS_ST_FILENAME = "neocd_st.rom";
|
||||
static const char* BIOS_ST_DESCRIPTION = "Top Loader (SMKDAN)";
|
||||
static const char* BIOS_Z_FILENAME = "neocd_z.rom";
|
||||
static const char* BIOS_Z_DESCRIPTION = "CDZ";
|
||||
static const char* BIOS_SZ_FILENAME = "neocd_sz.rom";
|
||||
static const char* BIOS_SZ_DESCRIPTION = "CDZ (SMKDAN)";
|
||||
|
||||
static const char* REGION_VARIABLE = "neocd_region";
|
||||
static const char* BIOS_VARIABLE = "neocd_bios";
|
||||
static const char* SPEEDHACK_VARIABLE = "neocd_cdspeedhack";
|
||||
static const char* LOADSKIP_VARIABLE = "neocd_loadskip";
|
||||
|
||||
static const struct retro_input_descriptor neogeoCDPadDescriptors[] = {
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "A" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "B" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "C" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "D" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
||||
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "A" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "B" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "C" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "D" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
||||
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static const uint8_t padMap[] = {
|
||||
RETRO_DEVICE_ID_JOYPAD_LEFT, Input::Left,
|
||||
RETRO_DEVICE_ID_JOYPAD_UP, Input::Up,
|
||||
RETRO_DEVICE_ID_JOYPAD_DOWN, Input::Down,
|
||||
RETRO_DEVICE_ID_JOYPAD_RIGHT, Input::Right,
|
||||
RETRO_DEVICE_ID_JOYPAD_B, Input::A,
|
||||
RETRO_DEVICE_ID_JOYPAD_A, Input::B,
|
||||
RETRO_DEVICE_ID_JOYPAD_Y, Input::C,
|
||||
RETRO_DEVICE_ID_JOYPAD_X, Input::D
|
||||
};
|
||||
|
||||
static const uint8_t padMap2[] = {
|
||||
0, RETRO_DEVICE_ID_JOYPAD_START, Input::Controller1Start,
|
||||
0, RETRO_DEVICE_ID_JOYPAD_SELECT, Input::Controller1Select,
|
||||
1, RETRO_DEVICE_ID_JOYPAD_START, Input::Controller2Start,
|
||||
1, RETRO_DEVICE_ID_JOYPAD_SELECT, Input::Controller2Select
|
||||
};
|
||||
|
||||
static const char* systemDirectory = nullptr;
|
||||
|
||||
LibretroCallbacks libretro = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
|
||||
|
||||
static std::vector<BiosListEntry> biosList;
|
||||
static int biosIndex = 0;
|
||||
static std::string biosChoices;
|
||||
|
||||
static bool skipCDLoading = true;
|
||||
static bool cdSpeedHack = false;
|
||||
|
||||
static std::vector<retro_variable> variables;
|
||||
|
||||
static std::string makeSystemPath(const std::string& filename)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if (systemDirectory)
|
||||
result = std::string(systemDirectory) + "/neocd/" + filename;
|
||||
else
|
||||
result = filename;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool fileExists(const std::string& filename)
|
||||
{
|
||||
std::ifstream file;
|
||||
file.open(filename);
|
||||
if (file.is_open())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void lookForBIOS()
|
||||
{
|
||||
biosList.clear();
|
||||
|
||||
if (fileExists(makeSystemPath(BIOS_F_FILENAME)))
|
||||
biosList.emplace_back(BiosListEntry(BIOS_F_FILENAME, BIOS_F_DESCRIPTION, NeoGeoCD::FrontLoader));
|
||||
|
||||
if (fileExists(makeSystemPath(BIOS_SF_FILENAME)))
|
||||
biosList.emplace_back(BiosListEntry(BIOS_SF_FILENAME, BIOS_SF_DESCRIPTION, NeoGeoCD::FrontLoader));
|
||||
|
||||
if (fileExists(makeSystemPath(BIOS_T_FILENAME)))
|
||||
biosList.emplace_back(BiosListEntry(BIOS_T_FILENAME, BIOS_T_DESCRIPTION, NeoGeoCD::TopLoader));
|
||||
|
||||
if (fileExists(makeSystemPath(BIOS_ST_FILENAME)))
|
||||
biosList.emplace_back(BiosListEntry(BIOS_ST_FILENAME, BIOS_ST_DESCRIPTION, NeoGeoCD::TopLoader));
|
||||
|
||||
if (fileExists(makeSystemPath(BIOS_Z_FILENAME)))
|
||||
biosList.emplace_back(BiosListEntry(BIOS_Z_FILENAME, BIOS_Z_DESCRIPTION, NeoGeoCD::CDZ));
|
||||
|
||||
if (fileExists(makeSystemPath(BIOS_SZ_FILENAME)))
|
||||
biosList.emplace_back(BiosListEntry(BIOS_SZ_FILENAME, BIOS_SZ_DESCRIPTION, NeoGeoCD::CDZ));
|
||||
}
|
||||
|
||||
static bool loadYZoomROM()
|
||||
{
|
||||
std::ifstream file;
|
||||
std::string filename;
|
||||
|
||||
filename = makeSystemPath("ng-lo.rom");
|
||||
file.open(filename, std::ios::in | std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
LOG(LOG_ERROR, "Could not load Y Zoom ROM %s\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
file.read(reinterpret_cast<char*>(neocd.memory.yZoomRom), Memory::YZOOMROM_SIZE);
|
||||
|
||||
if (file.gcount() < Memory::YZOOMROM_SIZE)
|
||||
{
|
||||
LOG(LOG_ERROR, "ng-lo.rom should be exactly 65536 bytes!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void patchROM_16(uint32_t address, uint16_t data)
|
||||
{
|
||||
*reinterpret_cast<uint16_t*>(&neocd.memory.rom[address & 0x7FFFF]) = BIG_ENDIAN_WORD(data);
|
||||
}
|
||||
|
||||
static void patchROM_32(uint32_t address, uint32_t data)
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>(&neocd.memory.rom[address & 0x7FFFF]) = BIG_ENDIAN_DWORD(data);
|
||||
}
|
||||
|
||||
static void patchROM_48(uint32_t address, uint32_t data1, uint16_t data2)
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>(&neocd.memory.rom[address & 0x7FFFF]) = BIG_ENDIAN_DWORD(data1);
|
||||
address += 4;
|
||||
*reinterpret_cast<uint16_t*>(&neocd.memory.rom[address & 0x7FFFF]) = BIG_ENDIAN_WORD(data2);
|
||||
}
|
||||
|
||||
static void installSpeedHack(uint32_t address)
|
||||
{
|
||||
patchROM_16(address, 0xFABE);
|
||||
}
|
||||
|
||||
static void disableSMKDANChecksum(uint32_t address)
|
||||
{
|
||||
patchROM_48(address, 0x22004E71, 0x4E71);
|
||||
}
|
||||
|
||||
static void patchBIOS()
|
||||
{
|
||||
bool isSMKDan = (biosList[biosIndex].filename == BIOS_SZ_FILENAME)
|
||||
|| (biosList[biosIndex].filename == BIOS_SF_FILENAME)
|
||||
|| (biosList[biosIndex].filename == BIOS_ST_FILENAME);
|
||||
|
||||
if (neocd.biosType == NeoGeoCD::FrontLoader)
|
||||
{
|
||||
// Speed hacks to avoid busy looping
|
||||
if (cdSpeedHack)
|
||||
{
|
||||
installSpeedHack(0xC10716);
|
||||
installSpeedHack(0xC10758);
|
||||
installSpeedHack(0xC10798);
|
||||
installSpeedHack(0xC10864);
|
||||
}
|
||||
|
||||
// If SMKDan, disable the BIOS checksum
|
||||
if (isSMKDan)
|
||||
disableSMKDANChecksum(0xC23EBE);
|
||||
}
|
||||
else if (neocd.biosType == NeoGeoCD::TopLoader)
|
||||
{
|
||||
// Speed hacks to avoid busy looping
|
||||
if (cdSpeedHack)
|
||||
{
|
||||
installSpeedHack(0xC0FFCA); // b
|
||||
installSpeedHack(0xC1000E); // a
|
||||
installSpeedHack(0xC1004E); // b
|
||||
installSpeedHack(0xC10120); // b
|
||||
}
|
||||
|
||||
// If SMKDan, disable the BIOS checksum
|
||||
if (isSMKDan)
|
||||
disableSMKDANChecksum(0xC23FBE);
|
||||
}
|
||||
else if (neocd.biosType == NeoGeoCD::CDZ)
|
||||
{
|
||||
// Patch the CD Recognition (It's done in the CD-ROM firmware, can't emulate)
|
||||
patchROM_16(0xC0D280, 0x4E71);
|
||||
|
||||
// Speed hacks to avoid busy looping
|
||||
if (cdSpeedHack)
|
||||
{
|
||||
installSpeedHack(0xC0E6E0);
|
||||
installSpeedHack(0xC0E724);
|
||||
installSpeedHack(0xC0E764);
|
||||
installSpeedHack(0xC0E836);
|
||||
installSpeedHack(0xC0E860);
|
||||
}
|
||||
|
||||
// If SMKDan, disable the BIOS checksum
|
||||
if (isSMKDan)
|
||||
disableSMKDANChecksum(0xC62BF4);
|
||||
}
|
||||
}
|
||||
|
||||
static bool loadBIOS()
|
||||
{
|
||||
std::ifstream file;
|
||||
std::string filename;
|
||||
|
||||
if (!biosList.size())
|
||||
{
|
||||
LOG(LOG_ERROR, "No BIOS detected!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
filename = makeSystemPath(biosList[biosIndex].filename);
|
||||
file.open(filename, std::ios::in | std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
LOG(LOG_ERROR, "Could not load BIOS %s\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
file.read(reinterpret_cast<char*>(neocd.memory.rom), Memory::ROM_SIZE);
|
||||
|
||||
if (file.gcount() < Memory::ROM_SIZE)
|
||||
{
|
||||
LOG(LOG_ERROR, "neocd.rom should be exactly 524288 bytes!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
neocd.biosType = biosList[biosIndex].type;
|
||||
|
||||
// Swab the BIOS if needed
|
||||
if (*reinterpret_cast<uint16_t*>(&neocd.memory.rom[0]) == 0x0010)
|
||||
{
|
||||
uint16_t* start = reinterpret_cast<uint16_t*>(&neocd.memory.rom[0]);
|
||||
uint16_t* end = reinterpret_cast<uint16_t*>(&neocd.memory.rom[Memory::ROM_SIZE]);
|
||||
|
||||
std::for_each(start, end, [](uint16_t& data) {
|
||||
data = BIG_ENDIAN_WORD(data);
|
||||
});
|
||||
}
|
||||
|
||||
patchBIOS();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int biosDescriptionToIndex(const char* description)
|
||||
{
|
||||
for (int result = 0; result < biosList.size(); ++result)
|
||||
{
|
||||
if (!strcmp(description, biosList[result].description.c_str()))
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void buildVariableList()
|
||||
{
|
||||
variables.clear();
|
||||
|
||||
variables.emplace_back(retro_variable{ REGION_VARIABLE, "Region; Japan|USA|Europe" });
|
||||
|
||||
biosChoices.clear();
|
||||
|
||||
if (biosList.size())
|
||||
{
|
||||
biosChoices = "BIOS Select; ";
|
||||
biosChoices.append(biosList[0].description);
|
||||
|
||||
for (size_t i = 1; i < biosList.size(); ++i)
|
||||
{
|
||||
biosChoices.append("|");
|
||||
biosChoices.append(biosList[i].description);
|
||||
}
|
||||
|
||||
variables.emplace_back(retro_variable{ BIOS_VARIABLE, biosChoices.c_str() });
|
||||
}
|
||||
|
||||
variables.emplace_back(retro_variable{ SPEEDHACK_VARIABLE, "CD Speed Hack; On|Off" });
|
||||
|
||||
variables.emplace_back(retro_variable{ LOADSKIP_VARIABLE, "Skip CD Loading; On|Off" });
|
||||
|
||||
variables.emplace_back(retro_variable{ nullptr, nullptr });
|
||||
}
|
||||
|
||||
static void loadBackupRam()
|
||||
{
|
||||
std::ifstream file;
|
||||
std::string filename;
|
||||
|
||||
filename = makeSystemPath("neocd.srm");
|
||||
file.open(filename, std::ios::in | std::ios::binary);
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
file.read(reinterpret_cast<char*>(neocd.memory.backupRam), Memory::BACKUPRAM_SIZE);
|
||||
file.close();
|
||||
}
|
||||
|
||||
static void saveBackupRam()
|
||||
{
|
||||
std::ofstream file;
|
||||
std::string filename;
|
||||
|
||||
filename = makeSystemPath("neocd.srm");
|
||||
file.open(filename, std::ios::out | std::ios::binary);
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
file.write(reinterpret_cast<char*>(neocd.memory.backupRam), Memory::BACKUPRAM_SIZE);
|
||||
file.close();
|
||||
}
|
||||
|
||||
static void updateVariables(bool needReset)
|
||||
{
|
||||
struct retro_variable var;
|
||||
|
||||
var.value = NULL;
|
||||
var.key = REGION_VARIABLE;
|
||||
|
||||
if (libretro.environment(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
||||
{
|
||||
uint32_t nationality = NeoGeoCD::NationalityJapan;
|
||||
|
||||
if (!strcmp(var.value, "USA"))
|
||||
nationality = NeoGeoCD::NationalityUSA;
|
||||
else if (!strcmp(var.value, "Europe"))
|
||||
nationality = NeoGeoCD::NationalityEurope;
|
||||
|
||||
if (neocd.machineNationality != nationality)
|
||||
{
|
||||
neocd.machineNationality = nationality;
|
||||
needReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
var.value = NULL;
|
||||
var.key = BIOS_VARIABLE;
|
||||
|
||||
if (libretro.environment(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
||||
{
|
||||
int index = biosDescriptionToIndex(var.value);
|
||||
|
||||
if (index != biosIndex)
|
||||
{
|
||||
biosIndex = index;
|
||||
loadBIOS();
|
||||
needReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
var.value = NULL;
|
||||
var.key = SPEEDHACK_VARIABLE;
|
||||
|
||||
if (libretro.environment(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
||||
{
|
||||
bool newValue = strcmp(var.value, "On") ? false : true;
|
||||
if (cdSpeedHack != newValue)
|
||||
{
|
||||
cdSpeedHack = newValue;
|
||||
loadBIOS();
|
||||
needReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
var.value = NULL;
|
||||
var.key = LOADSKIP_VARIABLE;
|
||||
|
||||
if (libretro.environment(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
||||
skipCDLoading = strcmp(var.value, "On") ? false : true;
|
||||
|
||||
if (needReset)
|
||||
neocd.reset();
|
||||
}
|
||||
|
||||
void retro_set_environment(retro_environment_t cb)
|
||||
{
|
||||
libretro.environment = cb;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t cb)
|
||||
{
|
||||
libretro.video = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample(retro_audio_sample_t cb)
|
||||
{
|
||||
UNUSED_ARG(cb);
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
|
||||
{
|
||||
libretro.audioBatch = cb;
|
||||
}
|
||||
|
||||
void retro_set_input_poll(retro_input_poll_t cb)
|
||||
{
|
||||
libretro.inputPoll = cb;
|
||||
}
|
||||
|
||||
void retro_set_input_state(retro_input_state_t cb)
|
||||
{
|
||||
libretro.inputState = cb;
|
||||
}
|
||||
|
||||
unsigned retro_api_version(void)
|
||||
{
|
||||
return RETRO_API_VERSION;
|
||||
}
|
||||
|
||||
void retro_set_controller_port_device(unsigned port, unsigned device)
|
||||
{
|
||||
UNUSED_ARG(port);
|
||||
UNUSED_ARG(device);
|
||||
}
|
||||
|
||||
void retro_get_system_info(struct retro_system_info *info)
|
||||
{
|
||||
std::memset(info, 0, sizeof(retro_system_info));
|
||||
|
||||
info->library_name = "NeoCD";
|
||||
info->library_version = "2018";
|
||||
info->valid_extensions = "cue";
|
||||
info->need_fullpath = true;
|
||||
}
|
||||
|
||||
void retro_get_system_av_info(struct retro_system_av_info *info)
|
||||
{
|
||||
std::memset(info, 0, sizeof(retro_system_av_info));
|
||||
|
||||
info->timing.fps = Timer::FRAME_RATE;
|
||||
info->timing.sample_rate = static_cast<double>(Audio::SAMPLE_RATE);
|
||||
info->geometry.base_width = Video::FRAMEBUFFER_WIDTH;
|
||||
info->geometry.base_height = Video::FRAMEBUFFER_HEIGHT;
|
||||
info->geometry.max_width = Video::FRAMEBUFFER_WIDTH;
|
||||
info->geometry.max_height = Video::FRAMEBUFFER_HEIGHT;
|
||||
info->geometry.aspect_ratio = Video::ASPECT_RATIO;
|
||||
}
|
||||
|
||||
size_t retro_serialize_size(void)
|
||||
{
|
||||
static size_t stateSize = 0;
|
||||
|
||||
if (!stateSize)
|
||||
{
|
||||
DataPacker dummy;
|
||||
neocd.saveState(dummy);
|
||||
stateSize = dummy.size();
|
||||
}
|
||||
|
||||
return stateSize;
|
||||
}
|
||||
|
||||
bool retro_serialize(void *data, size_t size)
|
||||
{
|
||||
if (size < retro_serialize_size())
|
||||
return false;
|
||||
|
||||
DataPacker state(reinterpret_cast<char*>(data), 0, size);
|
||||
return neocd.saveState(state);
|
||||
}
|
||||
|
||||
bool retro_unserialize(const void *data, size_t size)
|
||||
{
|
||||
if (size < retro_serialize_size())
|
||||
return false;
|
||||
|
||||
DataPacker state(const_cast<char*>(reinterpret_cast<const char*>(data)), size, size);
|
||||
|
||||
if (neocd.restoreState(state))
|
||||
return true;
|
||||
|
||||
loadBIOS();
|
||||
neocd.initialize();
|
||||
neocd.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
void retro_cheat_reset(void)
|
||||
{
|
||||
}
|
||||
|
||||
void retro_cheat_set(unsigned index, bool enabled, const char *code)
|
||||
{
|
||||
UNUSED_ARG(index);
|
||||
UNUSED_ARG(enabled);
|
||||
UNUSED_ARG(code);
|
||||
}
|
||||
|
||||
bool retro_load_game(const struct retro_game_info *info)
|
||||
{
|
||||
loadBackupRam();
|
||||
|
||||
libretro.environment(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, (void*)neogeoCDPadDescriptors);
|
||||
|
||||
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_RGB565;
|
||||
if (!libretro.environment(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
|
||||
{
|
||||
LOG(LOG_ERROR, "RGB565 support is required!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadYZoomROM())
|
||||
return false;
|
||||
|
||||
if (!loadBIOS())
|
||||
return false;
|
||||
|
||||
if (!neocd.cdrom.loadCue(info->path))
|
||||
return false;
|
||||
|
||||
// Load settings and reset
|
||||
updateVariables(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info)
|
||||
{
|
||||
UNUSED_ARG(game_type);
|
||||
UNUSED_ARG(info);
|
||||
UNUSED_ARG(num_info);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void retro_unload_game(void)
|
||||
{
|
||||
saveBackupRam();
|
||||
}
|
||||
|
||||
unsigned retro_get_region(void)
|
||||
{
|
||||
return RETRO_REGION_NTSC;
|
||||
}
|
||||
|
||||
void *retro_get_memory_data(unsigned id)
|
||||
{
|
||||
UNUSED_ARG(id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t retro_get_memory_size(unsigned id)
|
||||
{
|
||||
UNUSED_ARG(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void retro_reset(void)
|
||||
{
|
||||
neocd.reset();
|
||||
}
|
||||
|
||||
void retro_run(void)
|
||||
{
|
||||
bool updated = false;
|
||||
|
||||
if (libretro.environment(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
|
||||
updateVariables(false);
|
||||
|
||||
uint8_t input1 = 0xFF;
|
||||
uint8_t input2 = 0xFF;
|
||||
uint8_t input3 = 0x0F;
|
||||
|
||||
PROFILE(p_polling, ProfilingCategory::InputPolling);
|
||||
libretro.inputPoll();
|
||||
PROFILE_END(p_polling);
|
||||
|
||||
for (int i = 0; i < sizeof(padMap); i += 2)
|
||||
{
|
||||
if (libretro.inputState(0, RETRO_DEVICE_JOYPAD, 0, padMap[i]))
|
||||
input1 &= ~padMap[i + 1];
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizeof(padMap); i += 2)
|
||||
{
|
||||
if (libretro.inputState(1, RETRO_DEVICE_JOYPAD, 0, padMap[i]))
|
||||
input2 &= ~padMap[i + 1];
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizeof(padMap2); i += 3)
|
||||
{
|
||||
if (libretro.inputState(padMap2[i], RETRO_DEVICE_JOYPAD, 0, padMap2[i + 1]))
|
||||
input3 &= ~padMap2[i + 2];
|
||||
}
|
||||
|
||||
neocd.input.setInput(input1, input2, input3);
|
||||
|
||||
// Skip CD loading
|
||||
if (neocd.isIRQ1Enabled() && (neocd.lc8951.CTRL0 & LC8951::DECEN) && skipCDLoading)
|
||||
{
|
||||
neocd.fastForward = true;
|
||||
|
||||
while (neocd.isIRQ1Enabled() || neocd.irq1EnabledThisFrame)
|
||||
{
|
||||
neocd.irq1EnabledThisFrame = false;
|
||||
neocd.runOneFrame();
|
||||
|
||||
if (!neocd.irq1EnabledThisFrame && !(neocd.lc8951.CTRL0 & LC8951::DECEN))
|
||||
break;
|
||||
}
|
||||
|
||||
neocd.fastForward = false;
|
||||
}
|
||||
|
||||
neocd.irq1EnabledThisFrame = false;
|
||||
neocd.runOneFrame();
|
||||
|
||||
libretro.audioBatch(neocd.audio.audioBuffer, neocd.audio.samplesThisFrame);
|
||||
libretro.video(neocd.video.frameBuffer, Video::FRAMEBUFFER_WIDTH, Video::FRAMEBUFFER_HEIGHT, Video::FRAMEBUFFER_WIDTH * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
void retro_init(void)
|
||||
{
|
||||
struct retro_log_callback log;
|
||||
|
||||
if (libretro.environment(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
|
||||
libretro.log = log.log;
|
||||
else
|
||||
libretro.log = nullptr;
|
||||
|
||||
libretro.environment(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &systemDirectory);
|
||||
|
||||
// Initialize the emulation
|
||||
neocd.initialize();
|
||||
|
||||
// Init the backup RAM area
|
||||
std::memset(neocd.memory.backupRam, 0, sizeof(Memory::BACKUPRAM_SIZE));
|
||||
|
||||
// Search for all supported BIOSes
|
||||
lookForBIOS();
|
||||
|
||||
buildVariableList();
|
||||
|
||||
libretro.environment(RETRO_ENVIRONMENT_SET_VARIABLES, reinterpret_cast<void*>(&variables[0]));
|
||||
}
|
||||
|
||||
void retro_deinit(void)
|
||||
{
|
||||
// Deinitialize the emulation
|
||||
neocd.deinitialize();
|
||||
|
||||
#ifdef PROFILE_ENABLED
|
||||
LOG(LOG_INFO, "Total frame time: %f ms\n", g_profilingAccumulators[ProfilingCategory::Total]);
|
||||
LOG(LOG_INFO, " CD audio decoding: %f ms\n", g_profilingAccumulators[ProfilingCategory::AudioCD]);
|
||||
LOG(LOG_INFO, " YM2610 generation and sound mixing: %f ms\n", g_profilingAccumulators[ProfilingCategory::AudioYM2610]);
|
||||
LOG(LOG_INFO, " M68K emulation: %f ms\n", g_profilingAccumulators[ProfilingCategory::CpuM68K]);
|
||||
LOG(LOG_INFO, " Z80 emulation: %f ms\n", g_profilingAccumulators[ProfilingCategory::CpuZ80]);
|
||||
LOG(LOG_INFO, " Drawing video and handling IRQ: %f ms\n", g_profilingAccumulators[ProfilingCategory::VideoAndIRQ]);
|
||||
LOG(LOG_INFO, "Time spent polling inputs: %f ms\n", g_profilingAccumulators[ProfilingCategory::InputPolling]);
|
||||
|
||||
g_profilingAccumulatorsInitialized = false;
|
||||
#endif
|
||||
}
|
2461
src/libretro.h
Normal file
2461
src/libretro.h
Normal file
File diff suppressed because it is too large
Load Diff
164
src/m68kintf.cpp
Normal file
164
src/m68kintf.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
#include "m68kintf.h"
|
||||
#include "neogeocd.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void m68ki_exception_bus_error(void)
|
||||
{
|
||||
LOG(LOG_ERROR, "Bus Error @ PC=%X.", REG_PPC);
|
||||
|
||||
uint_fast32_t sr = m68ki_init_exception();
|
||||
|
||||
/* If we were processing a bus error, address error, or reset,
|
||||
* this is a catastrophic failure.
|
||||
* Halt the CPU
|
||||
*/
|
||||
if (CPU_RUN_MODE == RUN_MODE_BERR_AERR_RESET)
|
||||
{
|
||||
// m68k_read_memory_8(0x00ffff01);
|
||||
CPU_STOPPED = STOP_LEVEL_HALT;
|
||||
return;
|
||||
}
|
||||
CPU_RUN_MODE = RUN_MODE_BERR_AERR_RESET;
|
||||
|
||||
/* Note: This is implemented for 68000 only! */
|
||||
m68ki_stack_frame_buserr(sr);
|
||||
|
||||
m68ki_jump_vector(EXCEPTION_BUS_ERROR);
|
||||
|
||||
/* Use up some clock cycles and undo the instruction's cycles */
|
||||
USE_CYCLES(CYC_EXCEPTION[EXCEPTION_BUS_ERROR] - CYC_INSTRUCTION[REG_IR]);
|
||||
|
||||
SET_CYCLES(CYC_INSTRUCTION[REG_IR]);
|
||||
}
|
||||
|
||||
uint32_t m68k_read_memory_8(uint32_t address)
|
||||
{
|
||||
const Memory::Region* region = neocd.memory.regionLookupTable[address / Memory::MEMORY_GRANULARITY];
|
||||
|
||||
if (!region)
|
||||
{
|
||||
m68ki_exception_bus_error();
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
if (region->flags & Memory::Region::ReadDirect)
|
||||
return region->readBase[address & region->addressMask];
|
||||
|
||||
if (region->flags & Memory::Region::ReadMapped)
|
||||
return region->handlers->readByte(address & region->addressMask);
|
||||
|
||||
// Memory::Region::ReadNop
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void m68k_write_memory_8(uint32_t address, uint32_t data)
|
||||
{
|
||||
const Memory::Region* region = neocd.memory.regionLookupTable[address / Memory::MEMORY_GRANULARITY];
|
||||
|
||||
if (!region)
|
||||
{
|
||||
m68ki_exception_bus_error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (region->flags & Memory::Region::WriteDirect)
|
||||
{
|
||||
region->writeBase[address & region->addressMask] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if (region->flags & Memory::Region::WriteMapped)
|
||||
{
|
||||
region->handlers->writeByte(address & region->addressMask, data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory::Region::WriteNop
|
||||
}
|
||||
|
||||
uint32_t m68k_read_memory_16(uint32_t address)
|
||||
{
|
||||
const Memory::Region* region = neocd.memory.regionLookupTable[address / Memory::MEMORY_GRANULARITY];
|
||||
|
||||
if (!region)
|
||||
{
|
||||
m68ki_exception_bus_error();
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
if (region->flags & Memory::Region::ReadDirect)
|
||||
return BIG_ENDIAN_WORD(*reinterpret_cast<const uint16_t*>(®ion->readBase[address & region->addressMask]));
|
||||
|
||||
if (region->flags & Memory::Region::ReadMapped)
|
||||
return region->handlers->readWord(address & region->addressMask);
|
||||
|
||||
// Memory::Region::ReadNop
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
void m68k_write_memory_16(uint32_t address, uint32_t data)
|
||||
{
|
||||
const Memory::Region* region = neocd.memory.regionLookupTable[address / Memory::MEMORY_GRANULARITY];
|
||||
|
||||
if (!region)
|
||||
{
|
||||
m68ki_exception_bus_error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (region->flags & Memory::Region::WriteDirect)
|
||||
{
|
||||
*reinterpret_cast<uint16_t*>(®ion->writeBase[address & region->addressMask]) = BIG_ENDIAN_WORD(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (region->flags & Memory::Region::WriteMapped)
|
||||
{
|
||||
region->handlers->writeWord(address & region->addressMask, data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory::Region::WriteNop
|
||||
}
|
||||
|
||||
uint32_t m68k_read_memory_32(uint32_t address)
|
||||
{
|
||||
return (m68k_read_memory_16(address) << 16) | m68k_read_memory_16(address + 2);
|
||||
}
|
||||
|
||||
void m68k_write_memory_32(uint32_t address, uint32_t data)
|
||||
{
|
||||
m68k_write_memory_16(address, data >> 16);
|
||||
m68k_write_memory_16(address + 2, data & 0xFFFF);
|
||||
}
|
||||
|
||||
/*
|
||||
Interrupt levels have been checked by hooking the interrupts
|
||||
and writing SR somewhere in memory, which reveals the interrupt level.
|
||||
|
||||
VBL IRQ ($68): SR = 2100
|
||||
CD-ROM IRQs ($54 & $58): SR = 2200
|
||||
SCANLINE IRQ ($64): SR = 2300
|
||||
*/
|
||||
int neocd_get_vector(int level)
|
||||
{
|
||||
int vector = M68K_INT_ACK_AUTOVECTOR;
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case 1:
|
||||
vector = 0x68 / 4;
|
||||
break;
|
||||
case 2:
|
||||
vector = neocd.cdromVector / 4;
|
||||
break;
|
||||
case 3:
|
||||
vector = 0x64 / 4;
|
||||
break;
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
}
|
23
src/m68kintf.h
Normal file
23
src/m68kintf.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef M68KINTF_H
|
||||
#define M68KINTF_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void m68ki_exception_bus_error(void);
|
||||
uint32_t m68k_read_memory_8(uint32_t address);
|
||||
void m68k_write_memory_8(uint32_t address, uint32_t data);
|
||||
uint32_t m68k_read_memory_16(uint32_t address);
|
||||
void m68k_write_memory_16(uint32_t address, uint32_t data);
|
||||
uint32_t m68k_read_memory_32(uint32_t address);
|
||||
void m68k_write_memory_32(uint32_t address, uint32_t data);
|
||||
int neocd_get_vector(int level);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // M68KINTF_H
|
656
src/memory.cpp
Normal file
656
src/memory.cpp
Normal file
@ -0,0 +1,656 @@
|
||||
#include "neogeocd.h"
|
||||
#include "memory.h"
|
||||
#include "memory_paletteram.h"
|
||||
#include "memory_cdintf.h"
|
||||
#include "memory_input.h"
|
||||
#include "memory_mapped.h"
|
||||
#include "memory_z80comm.h"
|
||||
#include "memory_backupram.h"
|
||||
#include "memory_video.h"
|
||||
#include "memory_switches.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
/*
|
||||
What you see on a real Neo Geo CD:
|
||||
|
||||
START END DESCRIPTION
|
||||
0x000000 0x00007F ROM vectors or RAM
|
||||
0x000080 0x1FFFFF RAM
|
||||
0x200000 0x2FFFFF Random data. Whatever is on the data bus?
|
||||
0x300000 0x31FFFF <Joystick Data><FF><repeated>
|
||||
0x320000 0x33FFFF <Audio Status><FF><repeated>
|
||||
0x340000 0x35FFFF <Joystick Data><FF><repeated>
|
||||
0x360000 0x37FFFF <FF><repeated>
|
||||
0x380000 0x39FFFF <Joystick Data><FF><repeated>
|
||||
0x3A0000 0x3BFFFF <FF><repeated>
|
||||
0x3C0000 0x3DFFFF <03><FF><03><FF><00><FF><counter><FF><repeated>
|
||||
0x3E0000 0x3FFFFF <FF><repeated>
|
||||
0x400000 0x4FFFFF Palette data, mirrored on the whole area. Read access produces snow.
|
||||
0x500000 0x7FFFFF BUS ERROR !
|
||||
0x800000 0x8FFFFF Backup RAM, mirrored on the whole area.
|
||||
0x900000 0xBFFFFF BUS ERROR !
|
||||
0xC00000 0xCFFFFF ROM, mirrored on the whole area.
|
||||
0xD00000 0xDFFFFF BUS ERROR !
|
||||
0xE00000 0xEFFFFF Mapped area.
|
||||
0xF00000 0xFFFFFF BUS ERROR, except for the registers in the FF0000 range.
|
||||
*/
|
||||
|
||||
Memory::Memory() :
|
||||
vectorsMappedToRom(false),
|
||||
regionLookupTable(nullptr),
|
||||
ram(nullptr),
|
||||
rom(nullptr),
|
||||
sprRam(nullptr),
|
||||
fixRam(nullptr),
|
||||
pcmRam(nullptr),
|
||||
videoRam(nullptr),
|
||||
paletteRam(nullptr),
|
||||
yZoomRom(nullptr),
|
||||
z80Ram(nullptr),
|
||||
backupRam(nullptr),
|
||||
sprBankSelect(0),
|
||||
pcmBankSelect(0),
|
||||
busRequest(0),
|
||||
areaSelect(0),
|
||||
dmaSource(0),
|
||||
dmaDestination(0),
|
||||
dmaLength(0),
|
||||
dmaPattern(0),
|
||||
memoryRegions(),
|
||||
vectorRegions()
|
||||
{
|
||||
// Allocate the region lookup table
|
||||
regionLookupTable = reinterpret_cast<const Memory::Region**>(std::calloc(0x1000000 / MEMORY_GRANULARITY, sizeof(Memory::Region*)));
|
||||
|
||||
// Program RAM: 2MiB
|
||||
ram = reinterpret_cast<uint8_t*>(std::malloc(RAM_SIZE));
|
||||
|
||||
// ROM: 512KiB
|
||||
rom = reinterpret_cast<uint8_t*>(std::malloc(ROM_SIZE));
|
||||
|
||||
// SPR: 4MiB
|
||||
sprRam = reinterpret_cast<uint8_t*>(std::malloc(SPRRAM_SIZE));
|
||||
|
||||
// FIX: 128KiB
|
||||
fixRam = reinterpret_cast<uint8_t*>(std::malloc(FIXRAM_SIZE));
|
||||
|
||||
// PCM: 1MiB
|
||||
pcmRam = reinterpret_cast<uint8_t*>(std::malloc(PCMRAM_SIZE));
|
||||
|
||||
// Video RAM: 128KiB
|
||||
videoRam = reinterpret_cast<uint16_t*>(std::malloc(VIDEORAM_SIZE));
|
||||
|
||||
// Palette RAM: 16KiB
|
||||
paletteRam = reinterpret_cast<uint16_t*>(std::malloc(PALETTERAM_SIZE));
|
||||
|
||||
// Y Zoom Table ROM: 64KiB
|
||||
yZoomRom = reinterpret_cast<uint8_t*>(std::malloc(YZOOMROM_SIZE));
|
||||
|
||||
// Z80 RAM: 64KiB
|
||||
z80Ram = reinterpret_cast<uint8_t*>(std::malloc(Z80RAM_SIZE));
|
||||
|
||||
// Backup RAM: 8KiB
|
||||
backupRam = reinterpret_cast<uint8_t*>(std::malloc(BACKUPRAM_SIZE));
|
||||
|
||||
buildMemoryMap();
|
||||
initializeRegionLookupTable();
|
||||
mapVectorsToRom();
|
||||
}
|
||||
|
||||
Memory::~Memory()
|
||||
{
|
||||
if (backupRam)
|
||||
std::free(backupRam);
|
||||
|
||||
if (z80Ram)
|
||||
std::free(z80Ram);
|
||||
|
||||
if (yZoomRom)
|
||||
std::free(yZoomRom);
|
||||
|
||||
if (paletteRam)
|
||||
std::free(paletteRam);
|
||||
|
||||
if (videoRam)
|
||||
std::free(videoRam);
|
||||
|
||||
if (pcmRam)
|
||||
std::free(pcmRam);
|
||||
|
||||
if (fixRam)
|
||||
std::free(fixRam);
|
||||
|
||||
if (sprRam)
|
||||
std::free(sprRam);
|
||||
|
||||
if (rom)
|
||||
std::free(rom);
|
||||
|
||||
if (ram)
|
||||
std::free(ram);
|
||||
|
||||
if (regionLookupTable)
|
||||
std::free(regionLookupTable);
|
||||
}
|
||||
|
||||
void Memory::reset()
|
||||
{
|
||||
// Wipe clean all RAM
|
||||
std::memset(ram, 0, RAM_SIZE);
|
||||
std::memset(sprRam, 0, SPRRAM_SIZE);
|
||||
std::memset(fixRam, 0, FIXRAM_SIZE);
|
||||
std::memset(pcmRam, 0, PCMRAM_SIZE);
|
||||
std::memset(videoRam, 0, VIDEORAM_SIZE);
|
||||
std::memset(paletteRam, 0, PALETTERAM_SIZE);
|
||||
std::memset(z80Ram, 0, Z80RAM_SIZE);
|
||||
|
||||
// Map vector table to ROM
|
||||
mapVectorsToRom();
|
||||
|
||||
// Initialize DMA registers
|
||||
resetDma();
|
||||
|
||||
busRequest = 0;
|
||||
areaSelect = 0;
|
||||
|
||||
sprBankSelect = 0;
|
||||
pcmBankSelect = 0;
|
||||
}
|
||||
|
||||
void Memory::mapVectorsToRam()
|
||||
{
|
||||
regionLookupTable[0] = &vectorRegions[1];
|
||||
vectorsMappedToRom = false;
|
||||
}
|
||||
|
||||
void Memory::mapVectorsToRom()
|
||||
{
|
||||
regionLookupTable[0] = &vectorRegions[0];
|
||||
vectorsMappedToRom = true;
|
||||
}
|
||||
|
||||
void Memory::buildMemoryMap()
|
||||
{
|
||||
memoryRegions.clear();
|
||||
vectorRegions.clear();
|
||||
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadDirect | Memory::Region::WriteDirect), 0x000000, 0x1FFFFF, 0x001FFFFF, nullptr, neocd.memory.ram, neocd.memory.ram });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0x300000, 0x31FFFF, 0x00000001, &controller1Handlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0x320000, 0x33FFFF, 0x00000001, &z80CommunicationHandlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0x340000, 0x35FFFF, 0x00000001, &controller2Handlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0x380000, 0x39FFFF, 0x00000001, &controller3Handlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0x3A0000, 0x3BFFFF, 0x0000001F, &switchHandlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0x3C0000, 0x3DFFFF, 0x0000000F, &videoRamHandlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0x400000, 0x4FFFFF, 0x00001FFF, &paletteRamHandlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0x800000, 0x8FFFFF, 0x00003FFF, &backupRamHandlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadDirect | Memory::Region::WriteNop), 0xC00000, 0xCFFFFF, 0x0007FFFF, nullptr, neocd.memory.rom, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0xE00000, 0xEFFFFF, 0x000FFFFF, &mappedRamHandlers, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadMapped | Memory::Region::WriteMapped), 0xFF0000, 0xFF01FF, 0x000001FF, &cdInterfaceHandlers, nullptr, nullptr });
|
||||
|
||||
// Non essential areas
|
||||
|
||||
// The 0x2000000 area is normally random data (whatever is on the data bus?)
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadNop | Memory::Region::WriteNop), 0x200000, 0x2FFFFF, 0x00000000, nullptr, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadNop | Memory::Region::WriteNop), 0x360000, 0x37FFFF, 0x00000000, nullptr, nullptr, nullptr });
|
||||
memoryRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadNop | Memory::Region::WriteNop), 0x3E0000, 0x3FFFFF, 0x00000000, nullptr, nullptr, nullptr });
|
||||
|
||||
// Those regions are only used to swap the first 0x80 bytes of the address map between ROM and RAM
|
||||
vectorRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadDirect | Memory::Region::WriteNop), 0x000000, 0x00007F, 0x0000007F, nullptr, neocd.memory.rom, nullptr });
|
||||
vectorRegions.push_back({ static_cast<Memory::Region::Flags>(Memory::Region::ReadDirect | Memory::Region::WriteDirect), 0x000000, 0x00007F, 0x0000007F, nullptr, neocd.memory.ram, neocd.memory.ram });
|
||||
}
|
||||
|
||||
void Memory::initializeRegionLookupTable()
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(memoryRegions.size()); ++i)
|
||||
{
|
||||
for (const Memory::Region** ptr = ®ionLookupTable[memoryRegions[i].startAddress / MEMORY_GRANULARITY];
|
||||
ptr <= ®ionLookupTable[memoryRegions[i].endAddress / MEMORY_GRANULARITY];
|
||||
++ptr)
|
||||
*ptr = &memoryRegions[i];
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::doDma()
|
||||
{
|
||||
switch (dmaConfig[0])
|
||||
{
|
||||
case 0xFE3D:
|
||||
case 0xFE6D:
|
||||
dmaOpCopy();
|
||||
break;
|
||||
case 0xFFC5:
|
||||
case 0xFF89: // Used in Front Loader BIOS
|
||||
dmaOpCopyCdrom();
|
||||
break;
|
||||
case 0xFEF5:
|
||||
dmaOpFill();
|
||||
break;
|
||||
case 0xFFCD:
|
||||
case 0xFFDD:
|
||||
dmaOpPattern();
|
||||
break;
|
||||
case 0xE2DD:
|
||||
case 0xF2DD: // Used in Front Loader BIOS
|
||||
dmaOpCopyOddBytes();
|
||||
break;
|
||||
case 0xFC2D:
|
||||
dmaOpCopyCdromOddBytes();
|
||||
break;
|
||||
case 0xCFFD:
|
||||
dmaOpFillOddBytes();
|
||||
break;
|
||||
default:
|
||||
LOG(LOG_ERROR, "DMA transfer with unknown DMA configuration: %04x %04x %04x %04x %04x %04x %04x %04x %04x\n",
|
||||
dmaConfig[0],
|
||||
dmaConfig[1],
|
||||
dmaConfig[2],
|
||||
dmaConfig[3],
|
||||
dmaConfig[4],
|
||||
dmaConfig[5],
|
||||
dmaConfig[6],
|
||||
dmaConfig[7],
|
||||
dmaConfig[8]);
|
||||
LOG(LOG_ERROR, "Source : %X\n", dmaSource);
|
||||
LOG(LOG_ERROR, "Dest : %X\n", dmaDestination);
|
||||
LOG(LOG_ERROR, "Length : %X\n", dmaLength);
|
||||
LOG(LOG_ERROR, "Pattern: %X\n", dmaPattern);
|
||||
LOG(LOG_ERROR, "(PC = %X)\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::resetDma()
|
||||
{
|
||||
std::memset(dmaConfig, 0, sizeof(dmaConfig));
|
||||
dmaSource = 0;
|
||||
dmaDestination = 0;
|
||||
dmaLength = 0;
|
||||
dmaPattern = 0;
|
||||
}
|
||||
|
||||
const Memory::Region* Memory::dmaFindRegion(uint32_t address)
|
||||
{
|
||||
address &= 0xFFFFFF;
|
||||
|
||||
auto i = std::find_if(memoryRegions.cbegin(), memoryRegions.cend(), [&](const Memory::Region& region) {
|
||||
return ((address >= region.startAddress) && (address <= region.endAddress));
|
||||
});
|
||||
|
||||
if (i == memoryRegions.cend())
|
||||
return nullptr;
|
||||
|
||||
return &(*i);
|
||||
}
|
||||
|
||||
uint16_t Memory::dmaFetchNextWord(const Memory::Region* region, uint32_t& offset)
|
||||
{
|
||||
uint16_t value;
|
||||
|
||||
if (region->flags & Memory::Region::ReadDirect)
|
||||
value = BIG_ENDIAN_WORD(*reinterpret_cast<const uint16_t*>(®ion->readBase[offset & region->addressMask]));
|
||||
else if (region->flags & Memory::Region::ReadMapped)
|
||||
value = region->handlers->readWord(offset & region->addressMask);
|
||||
else // Memory::Region::ReadNop
|
||||
value = 0xFFFF;
|
||||
|
||||
offset += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
void Memory::dmaWriteNextWord(const Memory::Region* region, uint32_t& offset, uint16_t data)
|
||||
{
|
||||
if (region->flags & Memory::Region::WriteDirect)
|
||||
*reinterpret_cast<uint16_t*>(®ion->writeBase[offset & region->addressMask]) = BIG_ENDIAN_WORD(data);
|
||||
else if (region->flags & Memory::Region::WriteMapped)
|
||||
region->handlers->writeWord(offset & region->addressMask, data);
|
||||
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
void Memory::dmaOpCopyCdrom(void)
|
||||
{
|
||||
// Find the destination region
|
||||
const Memory::Region* region = dmaFindRegion(dmaDestination);
|
||||
if (!region)
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA COPY FROM CD BUFFER: Unknown destination region.\n");
|
||||
LOG(LOG_ERROR, "Dest : %X\n", dmaDestination);
|
||||
LOG(LOG_ERROR, "Length : %X\n", dmaLength);
|
||||
LOG(LOG_ERROR, "(PC = %X)\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Art of Fighting, after the first two matches will send DMA ops with length = 0x20000 when loading the bonus area selection screen
|
||||
This only happens with the CDZ Bios. Not sure what is really happening here. For now we fix it by patching the length :s
|
||||
*/
|
||||
if (dmaLength > 0x400)
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA transfer from CD buffer with length > 0x400 ! \n");
|
||||
m68k_write_memory_32(0x10FEFC, 0x800);
|
||||
dmaLength = 0x400;
|
||||
}
|
||||
else if (dmaLength < 0x400)
|
||||
LOG(LOG_ERROR, "DMA transfer from CD buffer with length = %X ! \n", dmaLength);
|
||||
|
||||
if (neocd.lc8951.IFSTAT & LC8951::DTBSY)
|
||||
LOG(LOG_ERROR, "DMA transfer from CD buffer but LC8951 side is not started ! \n");
|
||||
|
||||
if (neocd.lc8951.wordRegister(neocd.lc8951.DBCL, neocd.lc8951.DBCH) != 0x7FF)
|
||||
LOG(LOG_ERROR, "DMA transfer from CD buffer but LC8951 length is not 0x7FF ! \n");
|
||||
|
||||
uint16_t* source = reinterpret_cast<uint16_t*>(&neocd.lc8951.buffer[0]);
|
||||
uint32_t length = dmaLength;
|
||||
uint32_t offset = dmaDestination & region->addressMask;
|
||||
|
||||
while (length)
|
||||
{
|
||||
dmaWriteNextWord(region, offset, BIG_ENDIAN_WORD(*source));
|
||||
source++;
|
||||
length--;
|
||||
}
|
||||
|
||||
neocd.lc8951.endTransfer();
|
||||
}
|
||||
|
||||
void Memory::dmaOpCopyCdromOddBytes(void)
|
||||
{
|
||||
// Find the destination region
|
||||
const Memory::Region* region = dmaFindRegion(dmaDestination);
|
||||
if (!region)
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA COPY FROM CD BUFFER (ODD BYTES): Unknown destination region.\n");
|
||||
LOG(LOG_ERROR, "Dest : %X\n", dmaDestination);
|
||||
LOG(LOG_ERROR, "Length : %X\n", dmaLength);
|
||||
LOG(LOG_ERROR, "(PC = %X)\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
return;
|
||||
}
|
||||
|
||||
if (dmaLength > 0x400)
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA transfer from CD buffer with length > 0x400 ! \n");
|
||||
m68k_write_memory_32(0x10FEFC, 0x800); // Fix, see above.
|
||||
dmaLength = 0x400;
|
||||
}
|
||||
else if (dmaLength < 0x400)
|
||||
LOG(LOG_ERROR, "DMA transfer from CD buffer with length = %X ! \n", dmaLength);
|
||||
|
||||
if (neocd.lc8951.IFSTAT & LC8951::DTBSY)
|
||||
LOG(LOG_ERROR, "DMA transfer from CD buffer but LC8951 side is not started ! \n");
|
||||
|
||||
if (neocd.lc8951.wordRegister(neocd.lc8951.DBCL, neocd.lc8951.DBCH) != 0x7FF)
|
||||
LOG(LOG_ERROR, "DMA transfer from CD buffer but LC8951 length is not 0x7FF ! \n");
|
||||
|
||||
uint16_t* source = reinterpret_cast<uint16_t*>(&neocd.lc8951.buffer[0]);
|
||||
uint16_t data;
|
||||
uint32_t length = dmaLength;
|
||||
uint32_t offset = dmaDestination & region->addressMask;
|
||||
|
||||
while (length)
|
||||
{
|
||||
data = BIG_ENDIAN_WORD(*source);
|
||||
source++;
|
||||
dmaWriteNextWord(region, offset, data >> 8);
|
||||
dmaWriteNextWord(region, offset, data);
|
||||
length--;
|
||||
}
|
||||
|
||||
neocd.lc8951.endTransfer();
|
||||
}
|
||||
|
||||
void Memory::dmaOpCopyOddBytes(void)
|
||||
{
|
||||
// NOTE: dmaSource and dmaDestination are reversed for this
|
||||
|
||||
// Find the source region
|
||||
const Memory::Region* sourceRegion = dmaFindRegion(dmaDestination);
|
||||
|
||||
// Find the destination region
|
||||
const Memory::Region* destinationRegion = dmaFindRegion(dmaSource);
|
||||
|
||||
if ((!sourceRegion) || (!destinationRegion))
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA COPY ODD BYTES: Unhandled call\n");
|
||||
LOG(LOG_ERROR, "Source : %X\n", dmaDestination);
|
||||
LOG(LOG_ERROR, "Dest : %X\n", dmaSource);
|
||||
LOG(LOG_ERROR, "Length : %X\n", dmaLength);
|
||||
LOG(LOG_ERROR, "Pattern: %X\n", dmaPattern);
|
||||
LOG(LOG_ERROR, "(PC = %X)\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
return;
|
||||
}
|
||||
|
||||
// Do the copy
|
||||
|
||||
uint32_t sourceOffset = dmaDestination & sourceRegion->addressMask;
|
||||
uint32_t destinationOffset = dmaSource & destinationRegion->addressMask;
|
||||
uint16_t data;
|
||||
uint32_t length = dmaLength;
|
||||
|
||||
while (length)
|
||||
{
|
||||
data = dmaFetchNextWord(sourceRegion, sourceOffset);
|
||||
dmaWriteNextWord(destinationRegion, destinationOffset, BYTE_SWAP_16(data));
|
||||
dmaWriteNextWord(destinationRegion, destinationOffset, data);
|
||||
length--;
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::dmaOpCopy(void)
|
||||
{
|
||||
// NOTE: dmaSource and dmaDestination are reversed for this
|
||||
|
||||
// Find the source region
|
||||
const Memory::Region* sourceRegion = dmaFindRegion(dmaDestination);
|
||||
|
||||
// Find the destination region
|
||||
const Memory::Region* destinationRegion = dmaFindRegion(dmaSource);
|
||||
|
||||
if ((!sourceRegion) || (!destinationRegion))
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA COPY: Unhandled call\n");
|
||||
LOG(LOG_ERROR, "Source : %X\n", dmaDestination);
|
||||
LOG(LOG_ERROR, "Dest : %X\n", dmaSource);
|
||||
LOG(LOG_ERROR, "Length : %X\n", dmaLength);
|
||||
LOG(LOG_ERROR, "Pattern: %X\n", dmaPattern);
|
||||
LOG(LOG_ERROR, "(PC = %X)\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
return;
|
||||
}
|
||||
|
||||
// Do the copy
|
||||
|
||||
uint32_t sourceOffset = dmaDestination & sourceRegion->addressMask;
|
||||
uint32_t destinationOffset = dmaSource & destinationRegion->addressMask;
|
||||
uint16_t data;
|
||||
uint32_t length = dmaLength;
|
||||
|
||||
while (length)
|
||||
{
|
||||
data = dmaFetchNextWord(sourceRegion, sourceOffset);
|
||||
dmaWriteNextWord(destinationRegion, destinationOffset, data);
|
||||
length--;
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::dmaOpPattern(void)
|
||||
{
|
||||
// Find the destination region
|
||||
const Memory::Region* region = dmaFindRegion(dmaDestination);
|
||||
if (!region)
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA PATTERN: Unknown destination region.\n");
|
||||
LOG(LOG_ERROR, "Dest : %X\n", dmaDestination);
|
||||
LOG(LOG_ERROR, "Length : %X\n", dmaLength);
|
||||
LOG(LOG_ERROR, "Pattern: %X\n", dmaPattern);
|
||||
LOG(LOG_ERROR, "(PC = %X)\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill by writing the pattern
|
||||
uint32_t offset = dmaDestination & region->addressMask;
|
||||
uint32_t length = dmaLength;
|
||||
|
||||
while (length)
|
||||
{
|
||||
dmaWriteNextWord(region, offset, dmaPattern);
|
||||
length--;
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::dmaOpFill(void)
|
||||
{
|
||||
// Find the destination region
|
||||
const Memory::Region* region = dmaFindRegion(dmaDestination);
|
||||
if (!region)
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA FILL: Unknown destination region.\n");
|
||||
LOG(LOG_ERROR, "Dest : %X\n", dmaDestination);
|
||||
LOG(LOG_ERROR, "Length : %X\n", dmaLength);
|
||||
LOG(LOG_ERROR, "Pattern: %X\n", dmaPattern);
|
||||
LOG(LOG_ERROR, "(PC = %X)\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill by writing the current address
|
||||
|
||||
uint32_t address = dmaDestination;
|
||||
uint32_t offset = dmaDestination & region->addressMask;
|
||||
uint32_t length = dmaLength;
|
||||
|
||||
while (length)
|
||||
{
|
||||
dmaWriteNextWord(region, offset, (address >> 16));
|
||||
dmaWriteNextWord(region, offset, address);
|
||||
address += 4;
|
||||
length--;
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::dmaOpFillOddBytes(void)
|
||||
{
|
||||
// Find the destination region
|
||||
const Memory::Region* region = dmaFindRegion(dmaDestination);
|
||||
if (!region)
|
||||
{
|
||||
LOG(LOG_ERROR, "DMA FILL ODD BYTES: Unknown destination region.\n");
|
||||
LOG(LOG_ERROR, "Dest : %X\n", dmaDestination);
|
||||
LOG(LOG_ERROR, "Length : %X\n", dmaLength);
|
||||
LOG(LOG_ERROR, "Pattern: %X\n", dmaPattern);
|
||||
LOG(LOG_ERROR, "(PC = %X)\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill by writing the current address
|
||||
|
||||
uint32_t address = dmaDestination;
|
||||
uint32_t offset = dmaDestination & region->addressMask;
|
||||
uint32_t length = dmaLength;
|
||||
|
||||
while (length)
|
||||
{
|
||||
dmaWriteNextWord(region, offset, (address >> 24));
|
||||
dmaWriteNextWord(region, offset, (address >> 16));
|
||||
dmaWriteNextWord(region, offset, (address >> 8));
|
||||
dmaWriteNextWord(region, offset, address);
|
||||
address += 8;
|
||||
length--;
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::dumpDebugState()
|
||||
{
|
||||
std::ofstream file;
|
||||
|
||||
file.open("d:/temp/registers.bin", std::ios::out | std::ios::binary);
|
||||
|
||||
auto dumpRegValue = [&](m68k_register_t reg)
|
||||
{
|
||||
uint32_t regValue = m68k_get_reg(NULL, reg);
|
||||
file.write(reinterpret_cast<const char *>(®Value), sizeof(uint32_t));
|
||||
};
|
||||
|
||||
dumpRegValue(M68K_REG_D0);
|
||||
dumpRegValue(M68K_REG_D1);
|
||||
dumpRegValue(M68K_REG_D2);
|
||||
dumpRegValue(M68K_REG_D3);
|
||||
dumpRegValue(M68K_REG_D4);
|
||||
dumpRegValue(M68K_REG_D5);
|
||||
dumpRegValue(M68K_REG_D6);
|
||||
dumpRegValue(M68K_REG_D7);
|
||||
dumpRegValue(M68K_REG_A0);
|
||||
dumpRegValue(M68K_REG_A1);
|
||||
dumpRegValue(M68K_REG_A2);
|
||||
dumpRegValue(M68K_REG_A3);
|
||||
dumpRegValue(M68K_REG_A4);
|
||||
dumpRegValue(M68K_REG_A5);
|
||||
dumpRegValue(M68K_REG_A6);
|
||||
dumpRegValue(M68K_REG_SP);
|
||||
dumpRegValue(M68K_REG_PC);
|
||||
file.close();
|
||||
|
||||
file.open("d:/temp/ram.bin", std::ios::out | std::ios::binary);
|
||||
file.write(reinterpret_cast<const char *>(ram), Memory::RAM_SIZE);
|
||||
file.close();
|
||||
}
|
||||
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const Memory& memory)
|
||||
{
|
||||
out << memory.vectorsMappedToRom;
|
||||
out << memory.dmaConfig;
|
||||
out << memory.dmaSource;
|
||||
out << memory.dmaDestination;
|
||||
out << memory.dmaLength;
|
||||
out << memory.dmaPattern;
|
||||
out << memory.sprBankSelect;
|
||||
out << memory.pcmBankSelect;
|
||||
out << memory.busRequest;
|
||||
out << memory.areaSelect;
|
||||
|
||||
out.push(reinterpret_cast<const char*>(memory.ram), Memory::RAM_SIZE);
|
||||
out.push(reinterpret_cast<const char*>(memory.rom), Memory::ROM_SIZE);
|
||||
out.push(reinterpret_cast<const char*>(memory.sprRam), Memory::SPRRAM_SIZE);
|
||||
out.push(reinterpret_cast<const char*>(memory.fixRam), Memory::FIXRAM_SIZE);
|
||||
out.push(reinterpret_cast<const char*>(memory.pcmRam), Memory::PCMRAM_SIZE);
|
||||
out.push(reinterpret_cast<const char*>(memory.videoRam), Memory::VIDEORAM_SIZE);
|
||||
out.push(reinterpret_cast<const char*>(memory.paletteRam), Memory::PALETTERAM_SIZE);
|
||||
out.push(reinterpret_cast<const char*>(memory.z80Ram), Memory::Z80RAM_SIZE);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
DataPacker& operator>>(DataPacker& in, Memory& memory)
|
||||
{
|
||||
in >> memory.vectorsMappedToRom;
|
||||
|
||||
if (memory.vectorsMappedToRom)
|
||||
memory.mapVectorsToRom();
|
||||
else
|
||||
memory.mapVectorsToRam();
|
||||
|
||||
in >> memory.dmaConfig;
|
||||
in >> memory.dmaSource;
|
||||
in >> memory.dmaDestination;
|
||||
in >> memory.dmaLength;
|
||||
in >> memory.dmaPattern;
|
||||
in >> memory.sprBankSelect;
|
||||
in >> memory.pcmBankSelect;
|
||||
in >> memory.busRequest;
|
||||
in >> memory.areaSelect;
|
||||
|
||||
in.pop(reinterpret_cast<char*>(memory.ram), Memory::RAM_SIZE);
|
||||
in.pop(reinterpret_cast<char*>(memory.rom), Memory::ROM_SIZE);
|
||||
in.pop(reinterpret_cast<char*>(memory.sprRam), Memory::SPRRAM_SIZE);
|
||||
in.pop(reinterpret_cast<char*>(memory.fixRam), Memory::FIXRAM_SIZE);
|
||||
in.pop(reinterpret_cast<char*>(memory.pcmRam), Memory::PCMRAM_SIZE);
|
||||
in.pop(reinterpret_cast<char*>(memory.videoRam), Memory::VIDEORAM_SIZE);
|
||||
in.pop(reinterpret_cast<char*>(memory.paletteRam), Memory::PALETTERAM_SIZE);
|
||||
in.pop(reinterpret_cast<char*>(memory.z80Ram), Memory::Z80RAM_SIZE);
|
||||
|
||||
neocd.video.convertPalette();
|
||||
neocd.video.updateFixUsageMap();
|
||||
|
||||
return in;
|
||||
}
|
135
src/memory.h
Normal file
135
src/memory.h
Normal file
@ -0,0 +1,135 @@
|
||||
#ifndef MEMORY_H
|
||||
#define MEMORY_H
|
||||
|
||||
#include "endian.h"
|
||||
#include "datapacker.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class Memory
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t MEMORY_GRANULARITY = 0x80;
|
||||
static constexpr size_t RAM_SIZE = 0x200000;
|
||||
static constexpr size_t ROM_SIZE = 0x80000;
|
||||
static constexpr size_t SPRRAM_SIZE = 0x400000;
|
||||
static constexpr size_t FIXRAM_SIZE = 0x20000;
|
||||
static constexpr size_t PCMRAM_SIZE = 0x100000;
|
||||
static constexpr size_t VIDEORAM_SIZE = 0x20000;
|
||||
static constexpr size_t PALETTERAM_SIZE = 0x4000;
|
||||
static constexpr size_t YZOOMROM_SIZE = 0x10000;
|
||||
static constexpr size_t Z80RAM_SIZE = 0x10000;
|
||||
static constexpr size_t BACKUPRAM_SIZE = 0x2000;
|
||||
|
||||
typedef uint32_t(*ReadHandler)(uint32_t address);
|
||||
typedef void(*WriteHandler)(uint32_t address, uint32_t data);
|
||||
|
||||
struct Handlers
|
||||
{
|
||||
Memory::ReadHandler readByte;
|
||||
Memory::ReadHandler readWord;
|
||||
Memory::WriteHandler writeByte;
|
||||
Memory::WriteHandler writeWord;
|
||||
};
|
||||
|
||||
struct Region
|
||||
{
|
||||
enum Flags
|
||||
{
|
||||
ReadNop = 0x01,
|
||||
ReadMapped = 0x02,
|
||||
ReadDirect = 0x04,
|
||||
WriteNop = 0x08,
|
||||
WriteMapped = 0x10,
|
||||
WriteDirect = 0x20
|
||||
};
|
||||
|
||||
Memory::Region::Flags flags;
|
||||
uint32_t startAddress;
|
||||
uint32_t endAddress;
|
||||
uint32_t addressMask;
|
||||
const Memory::Handlers* handlers;
|
||||
const uint8_t* readBase;
|
||||
uint8_t* writeBase;
|
||||
};
|
||||
|
||||
enum MemoryArea
|
||||
{
|
||||
AREA_SPR = 1,
|
||||
AREA_PCM = 2,
|
||||
AREA_Z80 = 4,
|
||||
AREA_FIX = 8
|
||||
};
|
||||
|
||||
Memory();
|
||||
~Memory();
|
||||
|
||||
// Non copyable
|
||||
Memory(const Memory&) = delete;
|
||||
|
||||
// Non copyable
|
||||
Memory& operator=(const Memory&) = delete;
|
||||
|
||||
void reset();
|
||||
void mapVectorsToRam();
|
||||
void mapVectorsToRom();
|
||||
void doDma();
|
||||
void resetDma();
|
||||
|
||||
uint8_t* yZoomRom;
|
||||
uint8_t* backupRam;
|
||||
|
||||
// Variables to save in savestate
|
||||
bool vectorsMappedToRom;
|
||||
uint16_t dmaConfig[9];
|
||||
uint32_t dmaSource;
|
||||
uint32_t dmaDestination;
|
||||
uint32_t dmaLength;
|
||||
uint16_t dmaPattern;
|
||||
uint32_t sprBankSelect;
|
||||
uint32_t pcmBankSelect;
|
||||
uint32_t busRequest;
|
||||
uint32_t areaSelect;
|
||||
// Memory areas to save in savestate
|
||||
uint8_t* ram;
|
||||
uint8_t* rom;
|
||||
uint8_t* sprRam;
|
||||
uint8_t* fixRam;
|
||||
uint8_t* pcmRam;
|
||||
uint16_t* videoRam;
|
||||
uint16_t* paletteRam;
|
||||
uint8_t* z80Ram;
|
||||
// End variables to save in savestate
|
||||
|
||||
const Memory::Region** regionLookupTable;
|
||||
|
||||
friend DataPacker& operator<<(DataPacker& out, const Memory& memory);
|
||||
friend DataPacker& operator>>(DataPacker& in, Memory& memory);
|
||||
|
||||
protected:
|
||||
void buildMemoryMap();
|
||||
void initializeRegionLookupTable();
|
||||
|
||||
const Memory::Region* dmaFindRegion(uint32_t address);
|
||||
uint16_t dmaFetchNextWord(const Memory::Region* region, uint32_t& offset);
|
||||
void dmaWriteNextWord(const Memory::Region* region, uint32_t& offset, uint16_t data);
|
||||
void dmaOpCopyCdrom(void);
|
||||
void dmaOpCopyCdromOddBytes(void);
|
||||
void dmaOpCopyOddBytes(void);
|
||||
void dmaOpCopy(void);
|
||||
void dmaOpPattern(void);
|
||||
void dmaOpFill(void);
|
||||
void dmaOpFillOddBytes(void);
|
||||
|
||||
void dumpDebugState();
|
||||
|
||||
std::vector<Memory::Region> memoryRegions;
|
||||
std::vector<Memory::Region> vectorRegions;
|
||||
};
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const Memory& memory);
|
||||
DataPacker& operator>>(DataPacker& in, Memory& memory);
|
||||
|
||||
#endif // MEMORY_H
|
33
src/memory_backupram.cpp
Normal file
33
src/memory_backupram.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "memory_backupram.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
static uint32_t backupRamReadByte(uint32_t address)
|
||||
{
|
||||
if (address & 1)
|
||||
return neocd.memory.backupRam[address >> 1];
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static uint32_t backupRamReadWord(uint32_t address)
|
||||
{
|
||||
return (neocd.memory.backupRam[address >> 1] | 0xFF00);
|
||||
}
|
||||
|
||||
static void backupRamWriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
if (address & 1)
|
||||
neocd.memory.backupRam[address >> 1] = data;
|
||||
}
|
||||
|
||||
static void backupRamWriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
neocd.memory.backupRam[address >> 1] = data;
|
||||
}
|
||||
|
||||
const Memory::Handlers backupRamHandlers = {
|
||||
backupRamReadByte,
|
||||
backupRamReadWord,
|
||||
backupRamWriteByte,
|
||||
backupRamWriteWord
|
||||
};
|
8
src/memory_backupram.h
Normal file
8
src/memory_backupram.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MEMORY_BACKUPRAM_H
|
||||
#define MEMORY_BACKUPRAM_H
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
extern const Memory::Handlers backupRamHandlers;
|
||||
|
||||
#endif // MEMORY_BACKUPRAM_H
|
406
src/memory_cdintf.cpp
Normal file
406
src/memory_cdintf.cpp
Normal file
@ -0,0 +1,406 @@
|
||||
#include "memory_cdintf.h"
|
||||
#include "neogeocd.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
#include "3rdparty/z80/z80.h"
|
||||
#include "3rdparty/ym/ym2610.h"
|
||||
|
||||
/*
|
||||
Few notes for later:
|
||||
|
||||
- Some things are not 100% accurate: On a real machine when access to the SPR area is requested by writing to FF0121, the sprites disappear from screen.
|
||||
|
||||
- When access to mapped memory is released by writing to FF0141, the area at E00000 is mostly FF but random bits of data are flashing.
|
||||
|
||||
- SPR Bank select at FF01A1 seem to accept values 0-4. Any value above is the same as 4.
|
||||
I'm not entirely sure where 4 is supposed to point, that's theorically after the SPR ram.
|
||||
*/
|
||||
|
||||
static const uint8_t bitReverseTable[] = {
|
||||
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
|
||||
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
|
||||
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
|
||||
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
|
||||
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
|
||||
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
|
||||
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
|
||||
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
|
||||
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
|
||||
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
|
||||
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
|
||||
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
|
||||
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
|
||||
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
|
||||
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
|
||||
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
|
||||
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
|
||||
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
|
||||
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
|
||||
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
|
||||
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
|
||||
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
|
||||
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
|
||||
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
|
||||
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
|
||||
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
|
||||
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
|
||||
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
|
||||
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
|
||||
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
|
||||
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
|
||||
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
|
||||
};
|
||||
|
||||
inline uint16_t reverseBits(uint16_t value)
|
||||
{
|
||||
return (bitReverseTable[value & 0xFF] << 8) | (bitReverseTable[value >> 8]);
|
||||
}
|
||||
|
||||
static uint32_t cdInterfaceReadByte(uint32_t address)
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0x0017: // Unknown registers
|
||||
break;
|
||||
|
||||
case 0x0103: // FF0103: CDROM Read Register
|
||||
return neocd.lc8951.readRegister();
|
||||
|
||||
case 0x0161: // FF0161: CDROM Communication: Read Response Packet
|
||||
return neocd.lc8951.readResponsePacket();
|
||||
|
||||
case 0x0167: // FF0167: Unknown. Front loader BIOS uses this
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG(LOG_INFO, "CD-UNIT: Byte read from unknown register %06X @ PC=%06X\n", address + 0xFF0000, m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
break;
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
static uint32_t cdInterfaceReadWord(uint32_t address)
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0x0004: // FF0004: IRQ Mask for VBL & others
|
||||
// /!\ It is really important that this register can be read as it is saved on the stack and restored during an interrupt.
|
||||
return neocd.irqMask2;
|
||||
|
||||
case 0x011C: /*
|
||||
FF011C: System Config
|
||||
00ST00NN 00000000
|
||||
S = 1 to show an eject button in Top Loader BIOS
|
||||
N = Nationality
|
||||
T = Tray (Top & Front Models: Open=0 Close=1 CDZ: Open=1 Close=0)
|
||||
*/
|
||||
if (neocd.isCDZ())
|
||||
return ((~neocd.machineNationality & 7) << 8);
|
||||
return ((~neocd.machineNationality & 7) << 8) | 0x1000;
|
||||
|
||||
case 0x0188: // FF0188: Current CD-Audio Sample: Left Channel, bits are reversed
|
||||
if (neocd.cdrom.isPlaying() && neocd.cdrom.isAudio())
|
||||
return reverseBits((uint16_t)neocd.audio.cdAudioBuffer[neocd.audio.currentSample * 2]);
|
||||
|
||||
return 0x0000;
|
||||
|
||||
case 0x018A: // FF018A: Current CD-Audio Sample: Right Channel, bits are reversed
|
||||
if (neocd.cdrom.isPlaying() && neocd.cdrom.isAudio())
|
||||
return reverseBits((uint16_t)neocd.audio.cdAudioBuffer[neocd.audio.currentSample * 2 + 1]);
|
||||
|
||||
return 0x0000;
|
||||
|
||||
default:
|
||||
LOG(LOG_INFO, "CD-UNIT: Word read from unknown register %06X @ PC=%06X\n", address + 0xFF0000, m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
break;
|
||||
}
|
||||
|
||||
return 0x0000;
|
||||
}
|
||||
|
||||
static void cdInterfaceWriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0x000D: // Unknown Registers
|
||||
case 0x000E:
|
||||
case 0x0011:
|
||||
case 0x0015:
|
||||
case 0x0017:
|
||||
case 0x0167:
|
||||
case 0x016D:
|
||||
// LOG(LOG_INFO, "%06X = %02X\n", address + 0xFF0000, data);
|
||||
break;
|
||||
|
||||
case 0x000F: /*
|
||||
FF000F: CDROM IRQ Acknowledge
|
||||
$20 -> Vector $54 (CD-ROM decoder, IRQ1)
|
||||
$10 -> Vector $58 (CD-ROM communication, IRQ2)
|
||||
$08 -> Vector $5C (Unused)
|
||||
$04 -> Vector $60 (Unused, Spurious Interrupt)
|
||||
*/
|
||||
if (data & 0x20)
|
||||
neocd.clearInterrupt(NeoGeoCD::CdromDecoder);
|
||||
|
||||
if (data & 0x10)
|
||||
neocd.clearInterrupt(NeoGeoCD::CdromCommunication);
|
||||
|
||||
neocd.updateInterrupts();
|
||||
break;
|
||||
|
||||
case 0x0061: // FF0061: DMA control $40 = enable / $00 = clear
|
||||
if (data == 0x40)
|
||||
neocd.memory.doDma();
|
||||
else if (data == 0)
|
||||
neocd.memory.resetDma();
|
||||
break;
|
||||
|
||||
case 0x0101: // FF0101: CDROM Register Select
|
||||
neocd.lc8951.setRegisterPointer(data);
|
||||
break;
|
||||
|
||||
case 0x0103: // FF0103: CDROM Write Register
|
||||
neocd.lc8951.writeRegister(data);
|
||||
break;
|
||||
|
||||
case 0x0105: // FF0105: Destination Area (0=SPR; 1=PCM; 4=Z80; 5=FIX)
|
||||
switch (data)
|
||||
{
|
||||
case 0:
|
||||
neocd.memory.areaSelect = Memory::AREA_SPR;
|
||||
break;
|
||||
case 1:
|
||||
neocd.memory.areaSelect = Memory::AREA_PCM;
|
||||
break;
|
||||
case 4:
|
||||
neocd.memory.areaSelect = Memory::AREA_Z80;
|
||||
break;
|
||||
case 5:
|
||||
neocd.memory.areaSelect = Memory::AREA_FIX;
|
||||
break;
|
||||
default:
|
||||
neocd.memory.areaSelect = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0111: // FF0111: SPR Layer Enable / Disable
|
||||
neocd.video.sprDisable = (data != 0);
|
||||
break;
|
||||
|
||||
case 0x0115: // FF0115: FIX Layer Enable / Disable
|
||||
neocd.video.fixDisable = (data != 0);
|
||||
break;
|
||||
|
||||
case 0x0119: // FF0119: Video Enable / Disable
|
||||
neocd.video.videoEnable = (data != 0);
|
||||
break;
|
||||
|
||||
case 0x0121: // FF0121: SPR RAM Bus Request
|
||||
neocd.memory.busRequest |= Memory::AREA_SPR;
|
||||
break;
|
||||
|
||||
case 0x0123: // FF0123: PCM RAM Bus Request
|
||||
neocd.memory.busRequest |= Memory::AREA_PCM;
|
||||
break;
|
||||
|
||||
case 0x0127: // FF0127: Z80 RAM Bus Request
|
||||
neocd.memory.busRequest |= Memory::AREA_Z80;
|
||||
break;
|
||||
|
||||
case 0x0129: // FF0129: FIX RAM Bus Request
|
||||
neocd.memory.busRequest |= Memory::AREA_FIX;
|
||||
break;
|
||||
|
||||
case 0x0141: // FF0141: SPR RAM Bus Release
|
||||
neocd.memory.busRequest &= ~Memory::AREA_SPR;
|
||||
break;
|
||||
|
||||
case 0x0143: // FF0143: PCM RAM Bus Release
|
||||
neocd.memory.busRequest &= ~Memory::AREA_PCM;
|
||||
break;
|
||||
|
||||
case 0x0147: // FF0147: Z80 RAM Bus Release
|
||||
neocd.memory.busRequest &= ~Memory::AREA_Z80;
|
||||
break;
|
||||
|
||||
case 0x0149: // FF0149: FIX RAM Bus Release
|
||||
neocd.memory.busRequest &= ~Memory::AREA_FIX;
|
||||
neocd.video.updateFixUsageMap();
|
||||
break;
|
||||
|
||||
case 0x0163: // FF0163: CDROM Communication, Send Command Packet
|
||||
neocd.lc8951.writeCommandPacket(data);
|
||||
break;
|
||||
|
||||
case 0x0165: // FF0165: CDROM Communication, Access Pointer Increment + "Data Strobe" */
|
||||
neocd.lc8951.increasePacketPointer(data);
|
||||
break;
|
||||
|
||||
case 0x016F: // FF016F: Watchdog Timer $00 Enable / $01 Disable
|
||||
if (data)
|
||||
neocd.timers.watchdogTimer->setState(Timer::Stopped);
|
||||
else
|
||||
neocd.timers.watchdogTimer->setState(Timer::Active);
|
||||
break;
|
||||
|
||||
case 0x0181: /*
|
||||
FF0181: CD IRQ Master Enable (+Communication Reset ?)
|
||||
When data = 0, CD communication IRQ is disabled
|
||||
When data = 1, CD communication IRQ is enabled
|
||||
Not sure if CD decoder IRQ is affected too
|
||||
*/
|
||||
// LOG(LOG_INFO, "FF0181: %02X\n", data);
|
||||
neocd.irqMasterEnable = (data != 0);
|
||||
neocd.lc8951.resetPacketPointers();
|
||||
break;
|
||||
|
||||
case 0x0183: // FF0183: Z80 $00 Reset / $FF Enable
|
||||
if (!data)
|
||||
neocd.z80Disable = true;
|
||||
else
|
||||
{
|
||||
neocd.z80Disable = false;
|
||||
z80_reset();
|
||||
YM2610Reset();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x01A1: // FF01A1: SPR RAM Bank Select
|
||||
neocd.memory.sprBankSelect = data;
|
||||
break;
|
||||
|
||||
case 0x01A3: // FF01A3: PCM RAM Bank Select
|
||||
neocd.memory.pcmBankSelect = data;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG(LOG_INFO, "CD-UNIT: Write to unknown register %06X @ PC=%06X DATA=%02X\n", address + 0xFF0000, m68k_get_reg(NULL, M68K_REG_PPC), data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cdInterfaceWriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
// Unknown Registers
|
||||
case 0x0006:
|
||||
case 0x0008:
|
||||
case 0x000A:
|
||||
// LOG(LOG_INFO, "%06X = %04X\n", address + 0xFF0000, data);
|
||||
break;
|
||||
|
||||
case 0x0000: /*
|
||||
FF0000: CD-ROM Reset. (To be verified)
|
||||
*/
|
||||
// LOG(LOG_INFO, "CDROM: Drive reset at PC=%06X\n", m68k_get_reg(NULL, M68K_REG_PPC));
|
||||
neocd.cdrom.stop();
|
||||
neocd.lc8951.status = LC8951::CdIdle;
|
||||
break;
|
||||
|
||||
case 0x0002: /*
|
||||
FF0002: CDROM Interrupt Mask
|
||||
0x550
|
||||
0x050 IRQ2
|
||||
0x500 IRQ1
|
||||
*/
|
||||
neocd.irqMask1 = data;
|
||||
// LOG(LOG_INFO, "IRQ MASK1=%04X MASK2=%04X\n", neocd.irqMask1, neocd.irqMask2);
|
||||
|
||||
// Used to detect disc activity in the main loop
|
||||
if (neocd.isIRQ1Enabled())
|
||||
neocd.irq1EnabledThisFrame = true;
|
||||
break;
|
||||
|
||||
case 0x0004: /*
|
||||
FF0004: VBL Interrupt Mask
|
||||
0x731
|
||||
0x700 Unknown.
|
||||
0x030 VBL
|
||||
0x001 Unknown, writing zero causes a hard reset.
|
||||
|
||||
/!\ Emulating this register is vital !
|
||||
When the system is loading data from CD-ROM the VBL IRQ is disabled using this mask.
|
||||
The VBL IRQ reconfigure mapped memory access to peek into the Z80 RAM, if this register is not emulated the system will end up overwriting the Z80 RAM.
|
||||
*/
|
||||
neocd.irqMask2 = data;
|
||||
// LOG(LOG_INFO, "IRQ MASK1=%04X MASK2=%04X\n", neocd.irqMask1, neocd.irqMask2);
|
||||
break;
|
||||
|
||||
case 0x0064: // FF0064: DMA Destination Address High Word
|
||||
neocd.memory.dmaDestination = (neocd.memory.dmaDestination & 0xFFFF) | ((uint32_t)data << 16);
|
||||
break;
|
||||
|
||||
case 0x0066: // FF0066: DMA Destination Address Low Word
|
||||
neocd.memory.dmaDestination = (neocd.memory.dmaDestination & 0xFFFF0000) | (uint32_t)data;
|
||||
break;
|
||||
|
||||
case 0x0068: // FF0068: DMA Source Address High Word
|
||||
neocd.memory.dmaSource = (neocd.memory.dmaSource & 0xFFFF) | ((uint32_t)data << 16);
|
||||
break;
|
||||
|
||||
case 0x006A: // FF006A: DMA Source Address Low Word
|
||||
neocd.memory.dmaSource = (neocd.memory.dmaSource & 0xFFFF0000) | (uint32_t)data;
|
||||
break;
|
||||
|
||||
case 0x006C: // FF006C: DMA Pattern
|
||||
neocd.memory.dmaPattern = data;
|
||||
break;
|
||||
|
||||
case 0x0070: // FF0070: DMA Length High Word
|
||||
neocd.memory.dmaLength = (neocd.memory.dmaLength & 0xFFFF) | ((uint32_t)data << 16);
|
||||
break;
|
||||
|
||||
case 0x0072: // FF0072: DMA Length Low Word
|
||||
neocd.memory.dmaLength = (neocd.memory.dmaLength & 0xFFFF0000) | (uint32_t)data;
|
||||
break;
|
||||
|
||||
case 0x007E: // FF007E: DMA Configuration Register 0
|
||||
neocd.memory.dmaConfig[0] = data;
|
||||
break;
|
||||
|
||||
case 0x0080: // FF0080: DMA Configuration Register 1
|
||||
neocd.memory.dmaConfig[1] = data;
|
||||
break;
|
||||
|
||||
case 0x0082: // FF0082: DMA Configuration Register 2
|
||||
neocd.memory.dmaConfig[2] = data;
|
||||
break;
|
||||
|
||||
case 0x0084: // FF0084: DMA Configuration Register 3
|
||||
neocd.memory.dmaConfig[3] = data;
|
||||
break;
|
||||
|
||||
case 0x0086: // FF0086: DMA Configuration Register 4
|
||||
neocd.memory.dmaConfig[4] = data;
|
||||
break;
|
||||
|
||||
case 0x0088: // FF0088: DMA Configuration Register 5
|
||||
neocd.memory.dmaConfig[5] = data;
|
||||
break;
|
||||
|
||||
case 0x008A: // FF008A: DMA Configuration Register 6
|
||||
neocd.memory.dmaConfig[6] = data;
|
||||
break;
|
||||
|
||||
case 0x008C: // FF008C: DMA Configuration Register 7
|
||||
neocd.memory.dmaConfig[7] = data;
|
||||
break;
|
||||
|
||||
case 0x008E: // FF008E: DMA Configuration Register 8
|
||||
neocd.memory.dmaConfig[8] = data;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG(LOG_INFO, "CD-UNIT: Write to unknown register %06X @ PC=%06X DATA=%04X\n", address + 0xFF0000, m68k_get_reg(NULL, M68K_REG_PPC), data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const Memory::Handlers cdInterfaceHandlers = {
|
||||
cdInterfaceReadByte,
|
||||
cdInterfaceReadWord,
|
||||
cdInterfaceWriteByte,
|
||||
cdInterfaceWriteWord
|
||||
};
|
8
src/memory_cdintf.h
Normal file
8
src/memory_cdintf.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MEMORY_CDINTF_H
|
||||
#define MEMORY_CDINTF_H
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
extern const Memory::Handlers cdInterfaceHandlers;
|
||||
|
||||
#endif // MEMORY_CDINTF_H
|
144
src/memory_input.cpp
Normal file
144
src/memory_input.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
#include "memory_input.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
static uint32_t controller1ReadByte(uint32_t address)
|
||||
{
|
||||
if (!(address & 1))
|
||||
{
|
||||
switch (neocd.input.selector)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x12:
|
||||
case 0x1B:
|
||||
return neocd.input.input1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static uint32_t controller1ReadWord(uint32_t address)
|
||||
{
|
||||
switch (neocd.input.selector)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x12:
|
||||
case 0x1B:
|
||||
return ((neocd.input.input1 << 8) | 0xFF);
|
||||
}
|
||||
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
static void controller1WriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
if (address & 1)
|
||||
neocd.timers.watchdogTimer->setDelay(Timer::WATCHDOG_DELAY);
|
||||
}
|
||||
|
||||
static void controller1WriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
neocd.timers.watchdogTimer->setDelay(Timer::WATCHDOG_DELAY);
|
||||
}
|
||||
|
||||
const Memory::Handlers controller1Handlers = {
|
||||
controller1ReadByte,
|
||||
controller1ReadWord,
|
||||
controller1WriteByte,
|
||||
controller1WriteWord
|
||||
};
|
||||
|
||||
static uint32_t controller2ReadByte(uint32_t address)
|
||||
{
|
||||
if (!(address & 1))
|
||||
{
|
||||
switch (neocd.input.selector)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x12:
|
||||
case 0x1B:
|
||||
return neocd.input.input2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static uint32_t controller2ReadWord(uint32_t address)
|
||||
{
|
||||
switch (neocd.input.selector)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x12:
|
||||
case 0x1B:
|
||||
return ((neocd.input.input2 << 8) | 0xFF);
|
||||
}
|
||||
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
static void controller2WriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
// Nothing happens
|
||||
}
|
||||
|
||||
static void controller2WriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
// Nothing happens
|
||||
}
|
||||
|
||||
const Memory::Handlers controller2Handlers = {
|
||||
controller2ReadByte,
|
||||
controller2ReadWord,
|
||||
controller2WriteByte,
|
||||
controller2WriteWord
|
||||
};
|
||||
|
||||
static uint32_t controller3ReadByte(uint32_t address)
|
||||
{
|
||||
if (!(address & 1))
|
||||
{
|
||||
switch (neocd.input.selector)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x12:
|
||||
case 0x1B:
|
||||
return neocd.input.input3;
|
||||
}
|
||||
|
||||
return 0x0F;
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static uint32_t controller3ReadWord(uint32_t address)
|
||||
{
|
||||
switch (neocd.input.selector)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x12:
|
||||
case 0x1B:
|
||||
return ((neocd.input.input3 << 8) | 0xFF);
|
||||
}
|
||||
|
||||
return 0x0FFF;
|
||||
}
|
||||
|
||||
static void controller3WriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
if (address & 0x01)
|
||||
neocd.input.selector = data & 0xFF;
|
||||
}
|
||||
|
||||
static void controller3WriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
neocd.input.selector = data & 0xFF;
|
||||
}
|
||||
|
||||
const Memory::Handlers controller3Handlers = {
|
||||
controller3ReadByte,
|
||||
controller3ReadWord,
|
||||
controller3WriteByte,
|
||||
controller3WriteWord
|
||||
};
|
10
src/memory_input.h
Normal file
10
src/memory_input.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef MEMORY_INPUT_H
|
||||
#define MEMORY_INPUT_H
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
extern const Memory::Handlers controller1Handlers;
|
||||
extern const Memory::Handlers controller2Handlers;
|
||||
extern const Memory::Handlers controller3Handlers;
|
||||
|
||||
#endif // MEMORY_INPUT_H
|
157
src/memory_mapped.cpp
Normal file
157
src/memory_mapped.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
#include "memory_mapped.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
static uint32_t mappedRamReadByte(uint32_t address)
|
||||
{
|
||||
if (neocd.memory.areaSelect & neocd.memory.busRequest)
|
||||
{
|
||||
switch (neocd.memory.areaSelect)
|
||||
{
|
||||
case Memory::AREA_FIX:
|
||||
if (address & 1)
|
||||
{
|
||||
address = ((address >> 1) & 0x1FFFF);
|
||||
return neocd.memory.fixRam[address];
|
||||
}
|
||||
break;
|
||||
|
||||
case Memory::AREA_SPR:
|
||||
address += ((neocd.memory.sprBankSelect & 3) * 0x100000);
|
||||
address &= 0x3FFFFF;
|
||||
return neocd.memory.sprRam[address];
|
||||
break;
|
||||
|
||||
case Memory::AREA_Z80:
|
||||
if (address & 1)
|
||||
{
|
||||
address = ((address >> 1) & 0xFFFF);
|
||||
return neocd.memory.z80Ram[address];
|
||||
}
|
||||
break;
|
||||
|
||||
case Memory::AREA_PCM:
|
||||
if (address & 1)
|
||||
{
|
||||
address = ((address >> 1) + ((neocd.memory.pcmBankSelect & 1) * 0x80000)) & 0xFFFFF;
|
||||
return neocd.memory.pcmRam[address];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static uint32_t mappedRamReadWord(uint32_t address)
|
||||
{
|
||||
if (neocd.memory.areaSelect & neocd.memory.busRequest)
|
||||
{
|
||||
uint16_t *wordPtr;
|
||||
|
||||
switch (neocd.memory.areaSelect)
|
||||
{
|
||||
case Memory::AREA_FIX:
|
||||
address = ((address >> 1) & 0x1FFFF);
|
||||
return (neocd.memory.fixRam[address] | 0xFF00);
|
||||
break;
|
||||
|
||||
case Memory::AREA_SPR:
|
||||
address += ((neocd.memory.sprBankSelect & 3) * 0x100000);
|
||||
address &= 0x3FFFFE;
|
||||
wordPtr = (uint16_t*)&neocd.memory.sprRam[address];
|
||||
return BIG_ENDIAN_WORD(*wordPtr);
|
||||
break;
|
||||
|
||||
case Memory::AREA_Z80:
|
||||
address = ((address >> 1) & 0xFFFF);
|
||||
return (neocd.memory.z80Ram[address] | 0xFF00);
|
||||
break;
|
||||
|
||||
case Memory::AREA_PCM:
|
||||
address = ((address >> 1) + ((neocd.memory.pcmBankSelect & 1) * 0x80000)) & 0xFFFFF;
|
||||
return (neocd.memory.pcmRam[address] | 0xFF00);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
static void mappedRamWriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
if (neocd.memory.areaSelect & neocd.memory.busRequest)
|
||||
{
|
||||
switch (neocd.memory.areaSelect)
|
||||
{
|
||||
case Memory::AREA_FIX:
|
||||
if (address & 1)
|
||||
{
|
||||
address = ((address >> 1) & 0x1FFFF);
|
||||
neocd.memory.fixRam[address] = data;
|
||||
}
|
||||
break;
|
||||
|
||||
case Memory::AREA_SPR:
|
||||
address += ((neocd.memory.sprBankSelect & 3) * 0x100000);
|
||||
address &= 0x3FFFFF;
|
||||
neocd.memory.sprRam[address] = data;
|
||||
break;
|
||||
|
||||
case Memory::AREA_Z80:
|
||||
if (address & 1)
|
||||
{
|
||||
address = ((address >> 1) & 0xFFFF);
|
||||
neocd.memory.z80Ram[address] = data;
|
||||
}
|
||||
break;
|
||||
|
||||
case Memory::AREA_PCM:
|
||||
if (address & 1)
|
||||
{
|
||||
address = ((address >> 1) + ((neocd.memory.pcmBankSelect & 1) * 0x80000)) & 0xFFFFF;
|
||||
neocd.memory.pcmRam[address] = data;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mappedRamWriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
if (neocd.memory.areaSelect & neocd.memory.busRequest)
|
||||
{
|
||||
uint16_t *wordPtr;
|
||||
|
||||
switch (neocd.memory.areaSelect)
|
||||
{
|
||||
case Memory::AREA_FIX:
|
||||
address = ((address >> 1) & 0x1FFFF);
|
||||
neocd.memory.fixRam[address] = data;
|
||||
break;
|
||||
|
||||
case Memory::AREA_SPR:
|
||||
address += ((neocd.memory.sprBankSelect & 3) * 0x100000);
|
||||
address &= 0x3FFFFE;
|
||||
wordPtr = (uint16_t*)&neocd.memory.sprRam[address];
|
||||
*wordPtr = BIG_ENDIAN_WORD(data);
|
||||
break;
|
||||
|
||||
case Memory::AREA_Z80:
|
||||
address = ((address >> 1) & 0xFFFF);
|
||||
neocd.memory.z80Ram[address] = data;
|
||||
break;
|
||||
|
||||
case Memory::AREA_PCM:
|
||||
address = ((address >> 1) + ((neocd.memory.pcmBankSelect & 1) * 0x80000)) & 0xFFFFF;
|
||||
neocd.memory.pcmRam[address] = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Memory::Handlers mappedRamHandlers = {
|
||||
mappedRamReadByte,
|
||||
mappedRamReadWord,
|
||||
mappedRamWriteByte,
|
||||
mappedRamWriteWord
|
||||
};
|
8
src/memory_mapped.h
Normal file
8
src/memory_mapped.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MEMORY_MAPPED_H
|
||||
#define MEMORY_MAPPED_H
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
extern const Memory::Handlers mappedRamHandlers;
|
||||
|
||||
#endif // MEMORY_MAPPED_H
|
33
src/memory_paletteram.cpp
Normal file
33
src/memory_paletteram.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "memory_paletteram.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
static uint32_t paletteRamReadByte(uint32_t address)
|
||||
{
|
||||
return *(reinterpret_cast<uint8_t*>(&neocd.memory.paletteRam[neocd.video.activePaletteBank * 4096]) + address);
|
||||
}
|
||||
|
||||
static uint32_t paletteRamReadWord(uint32_t address)
|
||||
{
|
||||
return BIG_ENDIAN_WORD(neocd.memory.paletteRam[(neocd.video.activePaletteBank * 4096) + (address / 2)]);
|
||||
}
|
||||
|
||||
static void paletteRamWriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
uint32_t color = (neocd.video.activePaletteBank * 4096) + (address / 2);
|
||||
*(reinterpret_cast<uint8_t*>(&neocd.memory.paletteRam[neocd.video.activePaletteBank * 4096]) + address) = data;
|
||||
neocd.video.convertColor(color);
|
||||
}
|
||||
|
||||
static void paletteRamWriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
uint32_t color = (neocd.video.activePaletteBank * 4096) + (address / 2);
|
||||
neocd.memory.paletteRam[color] = BIG_ENDIAN_WORD(data);
|
||||
neocd.video.convertColor(color);
|
||||
}
|
||||
|
||||
const Memory::Handlers paletteRamHandlers = {
|
||||
paletteRamReadByte,
|
||||
paletteRamReadWord,
|
||||
paletteRamWriteByte,
|
||||
paletteRamWriteWord
|
||||
};
|
8
src/memory_paletteram.h
Normal file
8
src/memory_paletteram.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MEMORY_PALETTERAM_H
|
||||
#define MEMORY_PALETTERAM_H
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
extern const Memory::Handlers paletteRamHandlers;
|
||||
|
||||
#endif // MEMORY_PALETTERAM_H
|
63
src/memory_switches.cpp
Normal file
63
src/memory_switches.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "memory_switches.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
static uint32_t switchReadByte(uint32_t address)
|
||||
{
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static uint32_t switchReadWord(uint32_t address)
|
||||
{
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
/*
|
||||
The vector area affected by the switch is 0x80 bytes.
|
||||
When ROM is mapped to address 0, writing to the area has no effect.
|
||||
*/
|
||||
static void switchWriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0x00: // Darken colors, ignored for now
|
||||
case 0x10:
|
||||
break;
|
||||
|
||||
case 0x02: // Set ROM vectors
|
||||
neocd.memory.mapVectorsToRom();
|
||||
break;
|
||||
|
||||
case 0x0e: // Set Palette bank 0
|
||||
neocd.video.activePaletteBank = 0;
|
||||
break;
|
||||
|
||||
case 0x12: // Set RAM vectors
|
||||
neocd.memory.mapVectorsToRam();
|
||||
break;
|
||||
|
||||
case 0x1e: // Set palette bank 1
|
||||
neocd.video.activePaletteBank = 1;
|
||||
break;
|
||||
|
||||
default: // unknown
|
||||
LOG(LOG_INFO, "SWITCHES: Write to unknown switch %06X @ PC=%06X DATA=%04X\n", address + 0x3A0000, m68k_get_reg(NULL, M68K_REG_PPC), data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void switchWriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
if (address & 1)
|
||||
{
|
||||
address &= 0xFFFFFE;
|
||||
switchWriteWord(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
const Memory::Handlers switchHandlers = {
|
||||
switchReadByte,
|
||||
switchReadWord,
|
||||
switchWriteByte,
|
||||
switchWriteWord
|
||||
};
|
8
src/memory_switches.h
Normal file
8
src/memory_switches.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MEMORY_SWITCHES_H
|
||||
#define MEMORY_SWITCHES_H
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
extern const Memory::Handlers switchHandlers;
|
||||
|
||||
#endif // MEMORY_SWITCHES_H
|
110
src/memory_video.cpp
Normal file
110
src/memory_video.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
#include "memory_video.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
static uint32_t videoRamReadWord(uint32_t address)
|
||||
{
|
||||
uint32_t verticalPosition;
|
||||
|
||||
switch (address)
|
||||
{
|
||||
case 0x0: // Videoram Data
|
||||
case 0x2:
|
||||
return neocd.video.videoramData;
|
||||
|
||||
case 0x4: // Videoram Modulo
|
||||
return neocd.video.videoramModulo;
|
||||
|
||||
case 0x6: // Auto animation speed & H IRQ control
|
||||
verticalPosition = neocd.getScreenY() + 0x100;
|
||||
if (verticalPosition >= 0x200)
|
||||
verticalPosition = verticalPosition - Timer::SCREEN_HEIGHT;
|
||||
|
||||
return ((verticalPosition << 7) | (neocd.video.autoAnimationCounter & 7));
|
||||
}
|
||||
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
static void videoRamWriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0x0: // $3C0000: Videoram Offset
|
||||
neocd.video.videoramOffset = data;
|
||||
neocd.video.videoramData = neocd.memory.videoRam[neocd.video.videoramOffset];
|
||||
break;
|
||||
case 0x2: // $3C0002: Videoram Data
|
||||
neocd.memory.videoRam[neocd.video.videoramOffset] = data;
|
||||
neocd.video.videoramOffset = (neocd.video.videoramOffset & 0x8000) | ((neocd.video.videoramOffset + neocd.video.videoramModulo) & 0x7FFF);
|
||||
neocd.video.videoramData = neocd.memory.videoRam[neocd.video.videoramOffset];
|
||||
break;
|
||||
|
||||
case 0x4: // $3C0004: Videoram Modulo
|
||||
neocd.video.videoramModulo = data;
|
||||
break;
|
||||
|
||||
case 0x6: // $3C0006: Auto animation speed & H IRQ control
|
||||
neocd.video.autoAnimationSpeed = data >> 8;
|
||||
neocd.video.autoAnimationDisabled = (data & 0x0008) != 0;
|
||||
neocd.video.hirqControl = data & 0x00F0;
|
||||
break;
|
||||
|
||||
case 0x8: // $3C0008: Display counter high
|
||||
neocd.video.hirqRegister = (neocd.video.hirqRegister & 0x0000FFFF) | (data << 16);
|
||||
break;
|
||||
|
||||
case 0xA: // $3C000A: Display Counter low
|
||||
neocd.video.hirqRegister = (neocd.video.hirqRegister & 0xFFFF0000) | data;
|
||||
if (neocd.video.hirqControl & Video::HIRQ_CTRL_RELATIVE)
|
||||
{
|
||||
// Karnov uses this for raster effects, they calculate precisely the number of cycles to wait for the next line.
|
||||
// We need to take into account the number of cycles already executed in the timeslice.
|
||||
// A<--->t<----------->B (A=Current time for the emulator, t=time in the m68k timeslice, B=Set time for HIRQ)
|
||||
// A<--hirqReg--> B Too early.
|
||||
// A<--t-><--hirqReg-->B Correct.
|
||||
uint32_t timesliceElapsed = Timer::m68kToMaster(m68k_cycles_run());
|
||||
uint32_t delay = Timer::pixelToMaster(neocd.video.hirqRegister + 1);
|
||||
neocd.timers.hirqTimer->arm(timesliceElapsed + delay);
|
||||
}
|
||||
break;
|
||||
case 0xC: // $3C000C: IRQ Acknowledge
|
||||
if (data & 0x02)
|
||||
neocd.clearInterrupt(NeoGeoCD::Raster);
|
||||
if (data & 0x04)
|
||||
neocd.clearInterrupt(NeoGeoCD::VerticalBlank);
|
||||
|
||||
neocd.updateInterrupts();
|
||||
break;
|
||||
|
||||
case 0xE: /* $3C000E: Unknown */
|
||||
LOG(LOG_INFO, "VIDEO: Write to register $3C000E (Data=%04X)\n", data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t videoRamReadByte(uint32_t address)
|
||||
{
|
||||
if (!(address & 1))
|
||||
return videoRamReadWord(address & 6) >> 8;
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static void videoRamWriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
if (!(address & 1))
|
||||
{
|
||||
data = (data << 8) | data;
|
||||
videoRamWriteWord(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
const Memory::Handlers videoRamHandlers = {
|
||||
videoRamReadByte,
|
||||
videoRamReadWord,
|
||||
videoRamWriteByte,
|
||||
videoRamWriteWord
|
||||
};
|
8
src/memory_video.h
Normal file
8
src/memory_video.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MEMORY_VIDEO_H
|
||||
#define MEMORY_VIDEO_H
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
extern const Memory::Handlers videoRamHandlers;
|
||||
|
||||
#endif // MEMORY_VIDEO_H
|
48
src/memory_z80comm.cpp
Normal file
48
src/memory_z80comm.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "memory_z80comm.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
static uint32_t z80CommunicationReadByte(uint32_t address)
|
||||
{
|
||||
if (!address)
|
||||
return neocd.audioResult;
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static uint32_t z80CommunicationReadWord(uint32_t address)
|
||||
{
|
||||
return (neocd.audioResult << 8) | 0xFF;
|
||||
}
|
||||
|
||||
static void z80CommunicationWriteByte(uint32_t address, uint32_t data)
|
||||
{
|
||||
if (!address)
|
||||
{
|
||||
neocd.timers.audioCommandTimer->setUserData(data);
|
||||
|
||||
// 1 here, not zero otherwise the callback would be called immediately
|
||||
neocd.timers.audioCommandTimer->arm(1);
|
||||
|
||||
// End the 68K timeslice here so the Z80 can run up to the current point, then the pseudo audio command timer will trigger the IRQ
|
||||
m68k_end_timeslice();
|
||||
}
|
||||
}
|
||||
|
||||
static void z80CommunicationWriteWord(uint32_t address, uint32_t data)
|
||||
{
|
||||
neocd.timers.audioCommandTimer->setUserData(data >> 8);
|
||||
|
||||
// 1 here, not zero otherwise the callback would be called immediately
|
||||
neocd.timers.audioCommandTimer->arm(1);
|
||||
|
||||
// End the 68K timeslice here so the Z80 can run up to the current point, then the pseudo audio command timer will trigger the IRQ
|
||||
m68k_end_timeslice();
|
||||
}
|
||||
|
||||
const Memory::Handlers z80CommunicationHandlers = {
|
||||
z80CommunicationReadByte,
|
||||
z80CommunicationReadWord,
|
||||
z80CommunicationWriteByte,
|
||||
z80CommunicationWriteWord
|
||||
};
|
8
src/memory_z80comm.h
Normal file
8
src/memory_z80comm.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MEMORY_Z80COMM_H
|
||||
#define MEMORY_Z80COMM_H
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
extern const Memory::Handlers z80CommunicationHandlers;
|
||||
|
||||
#endif // MEMORY_Z80COMM_H
|
17
src/misc.h
Normal file
17
src/misc.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef MISC_H
|
||||
#define MISC_H
|
||||
|
||||
#ifndef UNUSED_ARG
|
||||
#define UNUSED_ARG(x) (void)(x)
|
||||
#endif
|
||||
|
||||
#define LOG_INFO RETRO_LOG_INFO
|
||||
#define LOG_ERROR RETRO_LOG_ERROR
|
||||
|
||||
#ifdef FALLBACK_LOG
|
||||
#define LOG(x, ...) fprintf(stdout, __VA_ARGS__)
|
||||
#else
|
||||
#define LOG(x, ...) if (libretro.log) libretro.log(x, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif // MISC_H
|
317
src/neogeocd.cpp
Normal file
317
src/neogeocd.cpp
Normal file
@ -0,0 +1,317 @@
|
||||
#include "neogeocd.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
#include "m68kintf.h"
|
||||
#include "3rdparty/z80/z80.h"
|
||||
#include "z80intf.h"
|
||||
#include "3rdparty/ym/ym2610.h"
|
||||
#include "timeprofiler.h"
|
||||
#include "timer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
NeoGeoCD neocd;
|
||||
|
||||
extern void cdromIRQTimerCallback(Timer* timer, uint32_t userData);
|
||||
extern void cdromIRQ1TimerCDZCallback(Timer* timer, uint32_t userData);
|
||||
|
||||
NeoGeoCD::NeoGeoCD() :
|
||||
machineNationality(NationalityJapan),
|
||||
cdromVector(0),
|
||||
cdzIrq1Divisor(0),
|
||||
irqMasterEnable(false),
|
||||
pendingInterrupts(0),
|
||||
remainingCyclesThisFrame(0),
|
||||
m68kCyclesThisFrame(0),
|
||||
z80CyclesThisFrame(0),
|
||||
z80TimeSlice(0),
|
||||
z80Disable(true),
|
||||
z80NMIDisable(true),
|
||||
currentTimeSeconds(0.0),
|
||||
fastForward(false),
|
||||
irq1EnabledThisFrame(false),
|
||||
audioCommand(0),
|
||||
audioResult(0),
|
||||
biosType(FrontLoader),
|
||||
irqMask1(0),
|
||||
irqMask2(0),
|
||||
memory(),
|
||||
video(),
|
||||
cdrom(),
|
||||
lc8951(),
|
||||
timers(),
|
||||
input(),
|
||||
audio()
|
||||
{
|
||||
}
|
||||
|
||||
void NeoGeoCD::initialize()
|
||||
{
|
||||
// Initialize the 68000 emulation core
|
||||
m68k_set_cpu_type(M68K_CPU_TYPE_68000);
|
||||
m68k_init();
|
||||
|
||||
// Inizialize the z80 core
|
||||
z80_init(0, Timer::Z80_CLOCK, NULL, z80_irq_callback);
|
||||
|
||||
// Initialize the YM2610
|
||||
YM2610Init(8000000, Audio::SAMPLE_RATE, neocd.memory.pcmRam, Memory::PCMRAM_SIZE, YM2610TimerHandler, YM2610IrqHandler);
|
||||
|
||||
// Create the worker thread to buffer & decode audio data
|
||||
neocd.cdrom.createWorkerThread();
|
||||
}
|
||||
|
||||
void NeoGeoCD::deinitialize()
|
||||
{
|
||||
// End the worker thread
|
||||
neocd.cdrom.endWorkerThread();
|
||||
}
|
||||
|
||||
void NeoGeoCD::reset()
|
||||
{
|
||||
memory.reset();
|
||||
video.reset();
|
||||
cdrom.reset();
|
||||
lc8951.reset();
|
||||
timers.reset();
|
||||
input.reset();
|
||||
audio.reset();
|
||||
|
||||
cdromVector = 0;
|
||||
irqMasterEnable = false;
|
||||
cdzIrq1Divisor = 0;
|
||||
pendingInterrupts = 0;
|
||||
irqMask1 = 0;
|
||||
irqMask2 = 0;
|
||||
remainingCyclesThisFrame = 0;
|
||||
m68kCyclesThisFrame = 0;
|
||||
z80CyclesThisFrame = 0;
|
||||
z80TimeSlice = 0;
|
||||
z80Disable = true;
|
||||
z80NMIDisable = true;
|
||||
currentTimeSeconds = 0.0;
|
||||
fastForward = false;
|
||||
audioCommand = 0;
|
||||
audioResult = 0;
|
||||
|
||||
m68k_pulse_reset();
|
||||
z80_reset();
|
||||
YM2610Reset();
|
||||
}
|
||||
|
||||
void NeoGeoCD::runOneFrame()
|
||||
{
|
||||
PROFILE(p_total, ProfilingCategory::Total);
|
||||
|
||||
remainingCyclesThisFrame += Timer::CYCLES_PER_FRAME;
|
||||
|
||||
PROFILE(p_audioCD, ProfilingCategory::AudioCD);
|
||||
audio.initFrame();
|
||||
PROFILE_END(p_audioCD);
|
||||
|
||||
while (remainingCyclesThisFrame > 0)
|
||||
{
|
||||
uint32_t timeSlice = std::min(neocd.timers.timeSlice(), remainingCyclesThisFrame);
|
||||
|
||||
PROFILE(p_m68k, ProfilingCategory::CpuM68K);
|
||||
uint32_t elapsed = Timer::m68kToMaster(m68k_execute(Timer::masterToM68k(timeSlice)));
|
||||
PROFILE_END(p_m68k);
|
||||
|
||||
m68kCyclesThisFrame += elapsed;
|
||||
|
||||
z80TimeSlice += elapsed;
|
||||
if (z80TimeSlice > 0)
|
||||
{
|
||||
uint32_t z80Elapsed;
|
||||
|
||||
if (z80Disable)
|
||||
z80Elapsed = z80TimeSlice;
|
||||
else
|
||||
{
|
||||
PROFILE(p_z80, ProfilingCategory::CpuZ80);
|
||||
z80Elapsed = Timer::z80ToMaster(z80_execute(Timer::masterToZ80(z80TimeSlice)));
|
||||
}
|
||||
|
||||
z80CyclesThisFrame += z80Elapsed;
|
||||
z80TimeSlice -= z80Elapsed;
|
||||
}
|
||||
|
||||
remainingCyclesThisFrame -= elapsed;
|
||||
|
||||
audio.updateCurrentSample();
|
||||
|
||||
currentTimeSeconds += Timer::masterToSeconds(elapsed);
|
||||
|
||||
PROFILE(p_videoIRQ, ProfilingCategory::VideoAndIRQ);
|
||||
neocd.timers.advanceTime(elapsed);
|
||||
PROFILE_END(p_videoIRQ);
|
||||
}
|
||||
|
||||
PROFILE(p_audioYM2610, ProfilingCategory::AudioYM2610);
|
||||
audio.finalize();
|
||||
PROFILE_END(p_audioYM2610);
|
||||
|
||||
m68kCyclesThisFrame -= Timer::CYCLES_PER_FRAME;
|
||||
z80CyclesThisFrame -= Timer::CYCLES_PER_FRAME;
|
||||
}
|
||||
|
||||
void NeoGeoCD::setInterrupt(NeoGeoCD::Interrupt interrupt)
|
||||
{
|
||||
pendingInterrupts |= interrupt;
|
||||
}
|
||||
|
||||
void NeoGeoCD::clearInterrupt(NeoGeoCD::Interrupt interrupt)
|
||||
{
|
||||
pendingInterrupts &= ~interrupt;
|
||||
}
|
||||
|
||||
int NeoGeoCD::updateInterrupts(void)
|
||||
{
|
||||
int level = 0;
|
||||
|
||||
if (pendingInterrupts & NeoGeoCD::VerticalBlank)
|
||||
level = 1;
|
||||
|
||||
if (pendingInterrupts & NeoGeoCD::CdromDecoder)
|
||||
{
|
||||
level = 2;
|
||||
cdromVector = 0x54;
|
||||
}
|
||||
|
||||
if (pendingInterrupts & NeoGeoCD::CdromCommunication)
|
||||
{
|
||||
level = 2;
|
||||
cdromVector = 0x58;
|
||||
}
|
||||
|
||||
if (pendingInterrupts & NeoGeoCD::Raster)
|
||||
level = 3;
|
||||
|
||||
m68k_set_irq(level);
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
int NeoGeoCD::getScreenX() const
|
||||
{
|
||||
return (Timer::masterToPixel(Timer::CYCLES_PER_FRAME - remainingCyclesThisFrame) % Timer::SCREEN_WIDTH);
|
||||
}
|
||||
|
||||
int NeoGeoCD::getScreenY() const
|
||||
{
|
||||
return (Timer::masterToPixel(Timer::CYCLES_PER_FRAME - remainingCyclesThisFrame) / Timer::SCREEN_WIDTH);
|
||||
}
|
||||
|
||||
bool NeoGeoCD::saveState(DataPacker& out) const
|
||||
{
|
||||
// General machine state
|
||||
out << cdzIrq1Divisor;
|
||||
out << irqMasterEnable;
|
||||
out << irqMask1;
|
||||
out << irqMask2;
|
||||
out << irq1EnabledThisFrame;
|
||||
out << fastForward;
|
||||
out << machineNationality;
|
||||
out << cdromVector;
|
||||
out << pendingInterrupts;
|
||||
out << remainingCyclesThisFrame;
|
||||
out << m68kCyclesThisFrame;
|
||||
out << z80CyclesThisFrame;
|
||||
out << z80TimeSlice;
|
||||
out << z80Disable;
|
||||
out << z80NMIDisable;
|
||||
out << currentTimeSeconds;
|
||||
out << audioCommand;
|
||||
out << audioResult;
|
||||
out << biosType;
|
||||
|
||||
// M68K
|
||||
out << m68ki_cpu;
|
||||
|
||||
// Z80
|
||||
out << Z80;
|
||||
|
||||
// Timers
|
||||
out << neocd.timers;
|
||||
|
||||
// Memory
|
||||
out << neocd.memory;
|
||||
|
||||
// Video
|
||||
out << neocd.video;
|
||||
|
||||
// Audio
|
||||
out << neocd.audio;
|
||||
|
||||
// YM2610
|
||||
YM2610SaveState(out);
|
||||
|
||||
// LC8951
|
||||
out << neocd.lc8951;
|
||||
|
||||
// CDROM
|
||||
out << neocd.cdrom;
|
||||
|
||||
return !out.fail();
|
||||
}
|
||||
|
||||
bool NeoGeoCD::restoreState(DataPacker& in)
|
||||
{
|
||||
// General machine state
|
||||
in >> cdzIrq1Divisor;
|
||||
in >> irqMasterEnable;
|
||||
in >> irqMask1;
|
||||
in >> irqMask2;
|
||||
in >> irq1EnabledThisFrame;
|
||||
in >> fastForward;
|
||||
in >> machineNationality;
|
||||
in >> cdromVector;
|
||||
in >> pendingInterrupts;
|
||||
in >> remainingCyclesThisFrame;
|
||||
in >> m68kCyclesThisFrame;
|
||||
in >> z80CyclesThisFrame;
|
||||
in >> z80TimeSlice;
|
||||
in >> z80Disable;
|
||||
in >> z80NMIDisable;
|
||||
in >> currentTimeSeconds;
|
||||
in >> audioCommand;
|
||||
in >> audioResult;
|
||||
in >> biosType;
|
||||
|
||||
// M68K
|
||||
in >> m68ki_cpu;
|
||||
m68k_set_cpu_type(M68K_CPU_TYPE_68000);
|
||||
m68ki_cpu.int_ack_callback = nullptr;
|
||||
m68ki_cpu.bkpt_ack_callback = nullptr;
|
||||
m68ki_cpu.reset_instr_callback = nullptr;
|
||||
m68ki_cpu.pc_changed_callback = nullptr;
|
||||
m68ki_cpu.set_fc_callback = nullptr;
|
||||
m68ki_cpu.instr_hook_callback = nullptr;
|
||||
|
||||
// Z80
|
||||
in >> Z80;
|
||||
Z80.daisy = nullptr;
|
||||
Z80.irq_callback = z80_irq_callback;
|
||||
|
||||
// Timers
|
||||
in >> neocd.timers;
|
||||
|
||||
// Memory
|
||||
in >> neocd.memory;
|
||||
|
||||
// Video
|
||||
in >> neocd.video;
|
||||
|
||||
// Audio
|
||||
in >> neocd.audio;
|
||||
|
||||
// YM2610
|
||||
YM2610RestoreState(in);
|
||||
|
||||
// LC8951
|
||||
in >> neocd.lc8951;
|
||||
|
||||
// CDROM
|
||||
in >> neocd.cdrom;
|
||||
|
||||
return (!in.fail());
|
||||
}
|
133
src/neogeocd.h
Normal file
133
src/neogeocd.h
Normal file
@ -0,0 +1,133 @@
|
||||
#ifndef NEOGEOCD_H
|
||||
#define NEOGEOCD_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "memory.h"
|
||||
#include "video.h"
|
||||
#include "cdrom.h"
|
||||
#include "lc8951.h"
|
||||
#include "timergroup.h"
|
||||
#include "input.h"
|
||||
#include "audio.h"
|
||||
#include "libretro.h"
|
||||
#include "misc.h"
|
||||
#include "datapacker.h"
|
||||
|
||||
class NeoGeoCD
|
||||
{
|
||||
public:
|
||||
|
||||
enum Nationality
|
||||
{
|
||||
NationalityJapan = 0,
|
||||
NationalityUSA = 1,
|
||||
NationalityEurope = 2,
|
||||
NationalityPortugal = 3
|
||||
};
|
||||
|
||||
enum BiosType {
|
||||
FrontLoader = 0,
|
||||
TopLoader = 1,
|
||||
CDZ = 2
|
||||
};
|
||||
|
||||
enum Interrupt {
|
||||
VerticalBlank = 1,
|
||||
CdromDecoder = 2,
|
||||
CdromCommunication = 4,
|
||||
Raster = 8
|
||||
};
|
||||
|
||||
NeoGeoCD();
|
||||
|
||||
// Non copyable
|
||||
NeoGeoCD(const NeoGeoCD&) = delete;
|
||||
|
||||
// Non copyable
|
||||
NeoGeoCD& operator=(const NeoGeoCD&) = delete;
|
||||
|
||||
void initialize();
|
||||
void deinitialize();
|
||||
|
||||
void reset();
|
||||
void runOneFrame();
|
||||
|
||||
void setInterrupt(NeoGeoCD::Interrupt interrupt);
|
||||
void clearInterrupt(NeoGeoCD::Interrupt interrupt);
|
||||
int updateInterrupts();
|
||||
|
||||
int getScreenX() const;
|
||||
int getScreenY() const;
|
||||
|
||||
inline bool isIRQ1Enabled() const
|
||||
{
|
||||
return ((irqMask1 & 0x500) == 0x500) && irqMasterEnable;
|
||||
}
|
||||
|
||||
inline bool isIRQ2Enabled() const
|
||||
{
|
||||
return ((irqMask1 & 0x50) == 0x50) && irqMasterEnable;
|
||||
}
|
||||
|
||||
inline bool isCDZ() const
|
||||
{
|
||||
return biosType == NeoGeoCD::CDZ;
|
||||
}
|
||||
|
||||
inline bool isVBLEnabled() const
|
||||
{
|
||||
return (irqMask2 & 0x030) == 0x030;
|
||||
}
|
||||
|
||||
bool saveState(DataPacker& out) const;
|
||||
bool restoreState(DataPacker& in);
|
||||
|
||||
Memory memory;
|
||||
Video video;
|
||||
Cdrom cdrom;
|
||||
LC8951 lc8951;
|
||||
TimerGroup timers;
|
||||
Input input;
|
||||
Audio audio;
|
||||
|
||||
// Variables to save in savestate
|
||||
uint32_t cdzIrq1Divisor;
|
||||
bool irqMasterEnable;
|
||||
uint32_t irqMask1;
|
||||
uint32_t irqMask2;
|
||||
bool irq1EnabledThisFrame;
|
||||
bool fastForward;
|
||||
uint32_t machineNationality;
|
||||
uint32_t cdromVector;
|
||||
uint32_t pendingInterrupts;
|
||||
int32_t remainingCyclesThisFrame;
|
||||
int32_t m68kCyclesThisFrame;
|
||||
int32_t z80CyclesThisFrame;
|
||||
int32_t z80TimeSlice;
|
||||
bool z80Disable;
|
||||
bool z80NMIDisable;
|
||||
double currentTimeSeconds;
|
||||
uint32_t audioCommand;
|
||||
uint32_t audioResult;
|
||||
BiosType biosType;
|
||||
|
||||
// End variables to save in savestate
|
||||
};
|
||||
|
||||
extern NeoGeoCD neocd;
|
||||
|
||||
struct LibretroCallbacks
|
||||
{
|
||||
retro_log_printf_t log;
|
||||
retro_video_refresh_t video;
|
||||
retro_input_poll_t inputPoll;
|
||||
retro_input_state_t inputState;
|
||||
retro_environment_t environment;
|
||||
retro_audio_sample_batch_t audioBatch;
|
||||
retro_perf_callback perf;
|
||||
};
|
||||
|
||||
extern LibretroCallbacks libretro;
|
||||
|
||||
#endif // NEOGEOCD_H
|
140
src/oggfile.cpp
Normal file
140
src/oggfile.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
#include "oggfile.h"
|
||||
|
||||
// Ogg read callback
|
||||
size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *datasource)
|
||||
{
|
||||
OggFile* oggFile = reinterpret_cast<OggFile*>(datasource);
|
||||
std::ifstream* file = oggFile->m_file;
|
||||
if ((!file) || (!file->is_open()))
|
||||
return 0;
|
||||
|
||||
file->read(static_cast<char*>(ptr), size * nmemb);
|
||||
|
||||
// Fix ifstream stupidity: eof is not a failure condition
|
||||
if (file->fail() && file->eof())
|
||||
file->clear();
|
||||
|
||||
return static_cast<size_t>(file->gcount());
|
||||
}
|
||||
|
||||
// Ogg seek callback
|
||||
int ogg_seek_cb(void *datasource, ogg_int64_t offset, int whence)
|
||||
{
|
||||
OggFile* oggFile = reinterpret_cast<OggFile*>(datasource);
|
||||
std::ifstream* file = oggFile->m_file;
|
||||
if ((!file) || (!file->is_open()))
|
||||
return -1;
|
||||
|
||||
std::ios::seekdir seekdir;
|
||||
|
||||
switch (whence)
|
||||
{
|
||||
case SEEK_CUR:
|
||||
seekdir = std::ios::cur;
|
||||
break;
|
||||
case SEEK_END:
|
||||
seekdir = std::ios::end;
|
||||
break;
|
||||
default:
|
||||
seekdir = std::ios::beg;
|
||||
break;
|
||||
}
|
||||
|
||||
file->seekg(offset, seekdir);
|
||||
if (file->fail())
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Ogg tell callback
|
||||
long ogg_tell_cb(void *datasource)
|
||||
{
|
||||
OggFile* oggFile = reinterpret_cast<OggFile*>(datasource);
|
||||
std::ifstream* file = oggFile->m_file;
|
||||
if ((!file) || (!file->is_open()))
|
||||
return -1;
|
||||
|
||||
return static_cast<long>(file->tellg());
|
||||
}
|
||||
|
||||
static const ov_callbacks ogg_callbacks = {
|
||||
ogg_read_cb,
|
||||
ogg_seek_cb,
|
||||
nullptr,
|
||||
ogg_tell_cb
|
||||
};
|
||||
|
||||
OggFile::OggFile() :
|
||||
m_isOpen(false),
|
||||
m_file(nullptr),
|
||||
m_vorbisFile()
|
||||
{
|
||||
}
|
||||
|
||||
OggFile::~OggFile()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
bool OggFile::initialize(std::ifstream *file)
|
||||
{
|
||||
cleanup();
|
||||
|
||||
m_file = file;
|
||||
|
||||
if (ov_open_callbacks(this, &m_vorbisFile, nullptr, 0, ogg_callbacks) != 0)
|
||||
return false;
|
||||
|
||||
m_isOpen = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t OggFile::read(char *data, size_t size)
|
||||
{
|
||||
if (!m_isOpen)
|
||||
return 0;
|
||||
|
||||
size_t done = 0;
|
||||
size_t result;
|
||||
int bitstream;
|
||||
|
||||
while (size)
|
||||
{
|
||||
result = ov_read(&m_vorbisFile, data, static_cast<int>(size), 0, 2, 1, &bitstream);
|
||||
if (result <= 0)
|
||||
return done;
|
||||
|
||||
data += result;
|
||||
done += result;
|
||||
size -= result;
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
bool OggFile::seek(size_t position)
|
||||
{
|
||||
if (!m_isOpen)
|
||||
return false;
|
||||
|
||||
return (ov_pcm_seek(&m_vorbisFile, position / 4) == 0);
|
||||
}
|
||||
|
||||
size_t OggFile::length()
|
||||
{
|
||||
if (!m_isOpen)
|
||||
return 0;
|
||||
|
||||
return (static_cast<size_t>(ov_pcm_total(&m_vorbisFile, -1) * 4));
|
||||
}
|
||||
|
||||
void OggFile::cleanup()
|
||||
{
|
||||
if (m_isOpen)
|
||||
{
|
||||
ov_clear(&m_vorbisFile);
|
||||
m_file = nullptr;
|
||||
m_isOpen = false;
|
||||
}
|
||||
}
|
41
src/oggfile.h
Normal file
41
src/oggfile.h
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef OGGFILE_H
|
||||
#define OGGFILE_H
|
||||
|
||||
#include "vorbis/vorbisfile.h"
|
||||
#include <fstream>
|
||||
#include <stdint.h>
|
||||
|
||||
class OggFile
|
||||
{
|
||||
public:
|
||||
OggFile();
|
||||
~OggFile();
|
||||
|
||||
// Non copyable
|
||||
OggFile(const OggFile&) = delete;
|
||||
|
||||
// Non copyable
|
||||
OggFile& operator=(const OggFile&) = delete;
|
||||
|
||||
bool initialize(std::ifstream* file);
|
||||
|
||||
size_t read(char *data, size_t size);
|
||||
|
||||
bool seek(size_t position);
|
||||
|
||||
size_t length();
|
||||
|
||||
void cleanup();
|
||||
|
||||
// Declare all callbacks as friends so they can modify the internals
|
||||
friend size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *datasource);
|
||||
friend int ogg_seek_cb(void *datasource, ogg_int64_t offset, int whence);
|
||||
friend long ogg_tell_cb(void *datasource);
|
||||
|
||||
protected:
|
||||
OggVorbis_File m_vorbisFile;
|
||||
std::ifstream* m_file;
|
||||
bool m_isOpen;
|
||||
};
|
||||
|
||||
#endif // OGGFILE_H
|
12
src/packedstruct.h
Normal file
12
src/packedstruct.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef PACKEDSTRUCT_H
|
||||
#define PACKEDSTRUCT_H
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define PACKED
|
||||
#elif (defined(__GNUC__) || defined(__clang__))
|
||||
#define PACKED __attribute__((packed))
|
||||
#else
|
||||
#error Unknown compiler!
|
||||
#endif
|
||||
|
||||
#endif // PACKEDSTRUCT_H
|
15
src/round.h
Normal file
15
src/round.h
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef ROUND_H
|
||||
#define ROUND_H 1
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
template<class I, class F>
|
||||
constexpr I round(const F& f)
|
||||
{
|
||||
static_assert(std::is_integral<I>::value, "I must be integral type");
|
||||
static_assert(std::is_floating_point<F>::value, "F must be floating point type");
|
||||
|
||||
return f >= F(0) ? I(f + F(0.5)) : I(f - F(I(f - F(1))) + F(0.5)) + I(f - F(1));
|
||||
}
|
||||
|
||||
#endif // ROUND_H
|
46
src/timeprofiler.cpp
Normal file
46
src/timeprofiler.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include "timeprofiler.h"
|
||||
|
||||
#ifdef PROFILE_ENABLED
|
||||
|
||||
#include <cstring>
|
||||
|
||||
bool g_profilingAccumulatorsInitialized = false;
|
||||
double g_profilingAccumulators[CategoryCount];
|
||||
|
||||
TimeProfiler::TimeProfiler(ProfilingCategory category) : m_category(category)
|
||||
{
|
||||
if (!g_profilingAccumulatorsInitialized)
|
||||
{
|
||||
std::memset(g_profilingAccumulators, 0, sizeof(g_profilingAccumulators));
|
||||
g_profilingAccumulatorsInitialized = true;
|
||||
}
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
TimeProfiler::~TimeProfiler()
|
||||
{
|
||||
end();
|
||||
}
|
||||
|
||||
void TimeProfiler::start()
|
||||
{
|
||||
m_start = std::chrono::high_resolution_clock::now();
|
||||
m_running = true;
|
||||
}
|
||||
|
||||
void TimeProfiler::end()
|
||||
{
|
||||
if (!m_running)
|
||||
return;
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
std::chrono::duration<double, std::milli> elapsed = end - m_start;
|
||||
|
||||
g_profilingAccumulators[m_category] += elapsed.count();
|
||||
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
#endif // PROFILE_ENABLED
|
52
src/timeprofiler.h
Normal file
52
src/timeprofiler.h
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef TIMEPROFILER_H
|
||||
#define TIMEPROFILER_H
|
||||
|
||||
//#define PROFILE_ENABLED
|
||||
|
||||
enum ProfilingCategory
|
||||
{
|
||||
Total = 0,
|
||||
AudioCD,
|
||||
AudioYM2610,
|
||||
CpuM68K,
|
||||
CpuZ80,
|
||||
VideoAndIRQ,
|
||||
InputPolling,
|
||||
CategoryCount
|
||||
};
|
||||
|
||||
#ifdef PROFILE_ENABLED
|
||||
|
||||
#include <chrono>
|
||||
|
||||
extern bool g_profilingAccumulatorsInitialized;
|
||||
extern double g_profilingAccumulators[CategoryCount];
|
||||
|
||||
class TimeProfiler
|
||||
{
|
||||
public:
|
||||
explicit TimeProfiler(ProfilingCategory category);
|
||||
virtual ~TimeProfiler();
|
||||
|
||||
void start();
|
||||
void end();
|
||||
|
||||
protected:
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_start;
|
||||
bool m_running;
|
||||
ProfilingCategory m_category;
|
||||
};
|
||||
|
||||
#define PROFILE(ObjectName, Category) TimeProfiler ObjectName(Category)
|
||||
#define PROFILE_START(ObjectName) ObjectName.start()
|
||||
#define PROFILE_END(ObjectName) ObjectName.end()
|
||||
|
||||
#else
|
||||
|
||||
#define PROFILE(ObjectName, Category)
|
||||
#define PROFILE_START(ObjectName)
|
||||
#define PROFILE_END(ObjectName)
|
||||
|
||||
#endif // PROFILE_ENABLED
|
||||
|
||||
#endif // TIMEPROFILER_H
|
139
src/timer.cpp
Normal file
139
src/timer.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
#include "timer.h"
|
||||
|
||||
/*
|
||||
drawlineTimer
|
||||
^
|
||||
|
|
||||
0 30 350 384
|
||||
0 +-----------------------+
|
||||
|#######################|
|
||||
16 |###+---------------+###|
|
||||
|###| 320 |###|
|
||||
|###| |###|
|
||||
|###| 224 |###|
|
||||
|###| |###|
|
||||
|###| |###|
|
||||
vblTimer -> 240 |###+---------------+###|
|
||||
|#######################|
|
||||
264 +-----------------------+
|
||||
|
||||
Pixel clock = 6000000 Hz
|
||||
Lines per second = 6000000 / 384 = 15625
|
||||
Frames per second = 15625 / 264 (approx 59.1856)
|
||||
|
||||
MC68000 clock = 12000000
|
||||
Cycles per pixel = 2
|
||||
Cycles per line = 768
|
||||
Cycles per screen = 202752
|
||||
|
||||
Z80 clock = 4000000
|
||||
Cycles per pixel = 4/6 (approx 0.6666)
|
||||
Cycles per line = 256
|
||||
Cycles per screen = 67584
|
||||
*/
|
||||
|
||||
Timer::Timer() :
|
||||
m_state(Timer::Stopped),
|
||||
m_callback(nullptr),
|
||||
m_delay(0),
|
||||
m_userData(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool Timer::isActive() const
|
||||
{
|
||||
return (m_state == Timer::Active);
|
||||
}
|
||||
|
||||
Timer::State Timer::state() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
void Timer::setState(const Timer::State &state)
|
||||
{
|
||||
m_state = state;
|
||||
checkTimeout();
|
||||
}
|
||||
|
||||
Timer::Callback Timer::callback() const
|
||||
{
|
||||
return m_callback;
|
||||
}
|
||||
|
||||
void Timer::setCallback(const Timer::Callback &callback)
|
||||
{
|
||||
m_callback = callback;
|
||||
}
|
||||
|
||||
int32_t Timer::delay() const
|
||||
{
|
||||
return m_delay;
|
||||
}
|
||||
|
||||
void Timer::setDelay(int32_t delay)
|
||||
{
|
||||
m_delay = delay;
|
||||
}
|
||||
|
||||
void Timer::arm(const int32_t delay)
|
||||
{
|
||||
m_delay = delay;
|
||||
m_state = Timer::Active;
|
||||
checkTimeout();
|
||||
}
|
||||
|
||||
void Timer::armRelative(const int32_t delay)
|
||||
{
|
||||
m_delay += delay;
|
||||
m_state = Timer::Active;
|
||||
checkTimeout();
|
||||
}
|
||||
|
||||
void Timer::advanceTime(const int32_t time)
|
||||
{
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
m_delay -= time;
|
||||
checkTimeout();
|
||||
}
|
||||
|
||||
uint32_t Timer::userData() const
|
||||
{
|
||||
return m_userData;
|
||||
}
|
||||
|
||||
void Timer::setUserData(const uint32_t &userData)
|
||||
{
|
||||
m_userData = userData;
|
||||
}
|
||||
|
||||
void Timer::checkTimeout()
|
||||
{
|
||||
if (!isActive() || (m_delay > 0))
|
||||
return;
|
||||
|
||||
m_state = Timer::Stopped;
|
||||
|
||||
if (m_callback)
|
||||
m_callback(this, m_userData);
|
||||
}
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const Timer& timer)
|
||||
{
|
||||
out << timer.m_state;
|
||||
out << timer.m_delay;
|
||||
out << timer.m_userData;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
DataPacker& operator>>(DataPacker& in, Timer& timer)
|
||||
{
|
||||
in >> timer.m_state;
|
||||
in >> timer.m_delay;
|
||||
in >> timer.m_userData;
|
||||
|
||||
return in;
|
||||
}
|
107
src/timer.h
Normal file
107
src/timer.h
Normal file
@ -0,0 +1,107 @@
|
||||
#ifndef TIMER_H
|
||||
#define TIMER_H
|
||||
|
||||
#include "datapacker.h"
|
||||
#include "round.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
static constexpr double MASTER_CLOCK = 24000000.0; //24167828.0;
|
||||
static constexpr double M68K_CLOCK = 12000000.0; //12083914.0;
|
||||
static constexpr double Z80_CLOCK = 4000000.0; //4027971.0;
|
||||
static constexpr double PIXEL_CLOCK = 6000000.0; //6041957.0;
|
||||
static constexpr int32_t SCREEN_WIDTH = 384;
|
||||
static constexpr int32_t SCREEN_HEIGHT = 264;
|
||||
static constexpr int32_t FIRST_LINE = 16;
|
||||
static constexpr int32_t VBLANK_LINE = FIRST_LINE + 224; // 240
|
||||
static constexpr int32_t HS_START = 30;
|
||||
static constexpr int32_t WATCHDOG_DELAY = round<int32_t>(MASTER_CLOCK * 0.13516792);
|
||||
static constexpr int32_t CDROM_DELAY = round<int32_t>(MASTER_CLOCK / 75.0);
|
||||
static constexpr int32_t CYCLES_PER_FRAME = round<int32_t>((MASTER_CLOCK / PIXEL_CLOCK) * SCREEN_WIDTH * SCREEN_HEIGHT);
|
||||
static constexpr double FRAME_RATE = /*PIXEL_CLOCK*/ 6041957.0 / static_cast<double>(SCREEN_WIDTH * SCREEN_HEIGHT);
|
||||
|
||||
typedef void(*Callback)(Timer* timer, uint32_t userData);
|
||||
|
||||
enum State
|
||||
{
|
||||
Stopped = 0,
|
||||
Active
|
||||
};
|
||||
|
||||
Timer();
|
||||
|
||||
bool isActive() const;
|
||||
Timer::State state() const;
|
||||
void setState(const Timer::State &state);
|
||||
|
||||
Timer::Callback callback() const;
|
||||
void setCallback(const Timer::Callback &callback);
|
||||
|
||||
int32_t delay() const;
|
||||
void setDelay(int32_t delay);
|
||||
void arm(const int32_t delay);
|
||||
void armRelative(const int32_t delay);
|
||||
void advanceTime(const int32_t time);
|
||||
|
||||
uint32_t userData() const;
|
||||
void setUserData(const uint32_t &userData);
|
||||
|
||||
static inline constexpr int32_t secondsToMaster(double value)
|
||||
{
|
||||
return round<int32_t>(value * MASTER_CLOCK);
|
||||
}
|
||||
|
||||
static inline constexpr double masterToSeconds(int32_t value)
|
||||
{
|
||||
return static_cast<double>(value) / MASTER_CLOCK;
|
||||
}
|
||||
|
||||
static inline constexpr int32_t m68kToMaster(int32_t value)
|
||||
{
|
||||
return round<int32_t>(static_cast<double>(value) * (MASTER_CLOCK / M68K_CLOCK));
|
||||
}
|
||||
|
||||
static inline constexpr int32_t z80ToMaster(int32_t value)
|
||||
{
|
||||
return round<int32_t>(static_cast<double>(value) * (MASTER_CLOCK / Z80_CLOCK));
|
||||
}
|
||||
|
||||
static inline constexpr int32_t pixelToMaster(int32_t value)
|
||||
{
|
||||
return round<int32_t>(static_cast<double>(value) * (MASTER_CLOCK / PIXEL_CLOCK));
|
||||
}
|
||||
|
||||
static inline constexpr int32_t masterToM68k(int32_t value)
|
||||
{
|
||||
return round<int32_t>(static_cast<double>(value) / (MASTER_CLOCK / M68K_CLOCK));
|
||||
}
|
||||
|
||||
static inline constexpr int32_t masterToZ80(int32_t value)
|
||||
{
|
||||
return round<int32_t>(static_cast<double>(value) / (MASTER_CLOCK / Z80_CLOCK));
|
||||
}
|
||||
|
||||
static inline constexpr int32_t masterToPixel(int32_t value)
|
||||
{
|
||||
return round<int32_t>(static_cast<double>(value) / (MASTER_CLOCK / PIXEL_CLOCK));
|
||||
}
|
||||
|
||||
friend DataPacker& operator<<(DataPacker& out, const Timer& timer);
|
||||
friend DataPacker& operator>>(DataPacker& in, Timer& timer);
|
||||
|
||||
protected:
|
||||
void checkTimeout();
|
||||
|
||||
Timer::State m_state;
|
||||
Timer::Callback m_callback;
|
||||
int32_t m_delay;
|
||||
uint32_t m_userData;
|
||||
};
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const Timer& timer);
|
||||
DataPacker& operator>>(DataPacker& in, Timer& timer);
|
||||
|
||||
#endif // TIMER_H
|
280
src/timergroup.cpp
Normal file
280
src/timergroup.cpp
Normal file
@ -0,0 +1,280 @@
|
||||
#include "timergroup.h"
|
||||
#include "neogeocd.h"
|
||||
#include "3rdparty/musashi/m68kcpu.h"
|
||||
#include "3rdparty/z80/z80.h"
|
||||
#include "3rdparty/ym/ym2610.h"
|
||||
#include "round.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
void watchdogTimerCallback(Timer* timer, uint32_t userData)
|
||||
{
|
||||
LOG(LOG_ERROR, "WARNING: Watchdog timer triggered (PC=%06X, SR=%04X); Machine reset.\n",
|
||||
m68k_get_reg(NULL, M68K_REG_PPC),
|
||||
m68k_get_reg(NULL, M68K_REG_SR));
|
||||
|
||||
m68k_pulse_reset();
|
||||
}
|
||||
|
||||
void vblTimerCallback(Timer* timer, uint32_t userData)
|
||||
{
|
||||
// Handle HIRQ_CTRL_VBLANK_LOAD
|
||||
if (neocd.video.hirqControl & Video::HIRQ_CTRL_VBLANK_LOAD)
|
||||
neocd.timers.hirqTimer->arm(timer->delay() + Timer::pixelToMaster(neocd.video.hirqRegister + 1));
|
||||
|
||||
// Set VBL IRQ to run
|
||||
if (neocd.isVBLEnabled())
|
||||
{
|
||||
neocd.setInterrupt(NeoGeoCD::VerticalBlank);
|
||||
neocd.updateInterrupts();
|
||||
}
|
||||
|
||||
// Update auto animation counter
|
||||
if (!neocd.video.autoAnimationFrameCounter)
|
||||
{
|
||||
neocd.video.autoAnimationFrameCounter = neocd.video.autoAnimationSpeed;
|
||||
neocd.video.autoAnimationCounter++;
|
||||
}
|
||||
else
|
||||
neocd.video.autoAnimationFrameCounter--;
|
||||
|
||||
// Set next VBL
|
||||
timer->armRelative(Timer::pixelToMaster(Timer::SCREEN_WIDTH * Timer::SCREEN_HEIGHT));
|
||||
}
|
||||
|
||||
void hirqTimerCallback(Timer* timer, uint32_t userData)
|
||||
{
|
||||
// Trigger horizontal interrupt if enabled
|
||||
if (neocd.video.hirqControl & Video::HIRQ_CTRL_ENABLE)
|
||||
{
|
||||
/* LOG(LOG_INFO, "Horizontal IRQ.@ (%d,%d), next drawline in : %d\n",
|
||||
neocd.getScreenX(),
|
||||
neocd.getScreenY(),
|
||||
Timer::masterToPixel(neocd.timers.drawlineTimer->delay()));*/
|
||||
|
||||
neocd.setInterrupt(NeoGeoCD::Raster);
|
||||
neocd.updateInterrupts();
|
||||
}
|
||||
|
||||
// Neo Drift Out sets HIRQ_reg = 0xFFFFFFFF and auto-repeat, we need to check for it
|
||||
// I don't know the exact reason for the need to add 1 to hirqRegister + 1 here and several other parts of the code,
|
||||
// but it's important for line effects in Karnov's Revenge to work correctly.
|
||||
if ((neocd.video.hirqControl & Video::HIRQ_CTRL_AUTOREPEAT) && (neocd.video.hirqRegister != 0xFFFFFFFF))
|
||||
timer->armRelative(Timer::pixelToMaster(std::max(uint32_t(1), neocd.video.hirqRegister + 1)));
|
||||
}
|
||||
|
||||
void ym2610TimerCallback(Timer* Timer, uint32_t data)
|
||||
{
|
||||
YM2610TimerOver(data);
|
||||
}
|
||||
|
||||
void drawlineTimerCallback(Timer* timer, uint32_t userData)
|
||||
{
|
||||
// LOG(LOG_INFO, "Drawline.@ (%d,%d)\n", neocd.getScreenX(), neocd.getScreenY());
|
||||
|
||||
uint32_t scanline = neocd.getScreenY();
|
||||
|
||||
// Video content is generated between scanlines 16 and 240, see timer.h
|
||||
if ((scanline >= Timer::FIRST_LINE) && (scanline < Timer::VBLANK_LINE))
|
||||
{
|
||||
if (!neocd.fastForward)
|
||||
{
|
||||
if (neocd.video.videoEnable)
|
||||
{
|
||||
neocd.video.drawEmptyLine(scanline);
|
||||
|
||||
if (!neocd.video.sprDisable)
|
||||
{
|
||||
if (scanline & 1)
|
||||
{
|
||||
uint16_t activeSprites = neocd.video.createSpriteList(scanline, &neocd.memory.videoRam[0x8680]);
|
||||
neocd.video.drawSprites(scanline, &neocd.memory.videoRam[0x8680], activeSprites);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint16_t activeSprites = neocd.video.createSpriteList(scanline, &neocd.memory.videoRam[0x8600]);
|
||||
neocd.video.drawSprites(scanline, &neocd.memory.videoRam[0x8600], activeSprites);
|
||||
}
|
||||
}
|
||||
|
||||
if (!neocd.video.fixDisable)
|
||||
neocd.video.drawFix(scanline);
|
||||
}
|
||||
else
|
||||
neocd.video.drawBlackLine(scanline);
|
||||
}
|
||||
}
|
||||
|
||||
timer->armRelative(Timer::pixelToMaster(Timer::SCREEN_WIDTH));
|
||||
}
|
||||
|
||||
void cdromIRQTimerCallback(Timer* timer, uint32_t userData)
|
||||
{
|
||||
timer->armRelative(neocd.isCDZ() ? (Timer::CDROM_DELAY / 2) : Timer::CDROM_DELAY);
|
||||
|
||||
if (neocd.cdrom.isPlaying())
|
||||
{
|
||||
neocd.lc8951.sectorDecoded();
|
||||
|
||||
// Trigger the CD IRQ1 if needed, this is used when reading data sectors
|
||||
if (neocd.isIRQ1Enabled() && (neocd.lc8951.IFCTRL & LC8951::DECIEN) && !(neocd.lc8951.IFSTAT & LC8951::DECI))
|
||||
neocd.setInterrupt(NeoGeoCD::CdromDecoder);
|
||||
|
||||
if (neocd.cdrom.isData())
|
||||
neocd.cdzIrq1Divisor = 0;
|
||||
else if (neocd.isCDZ())
|
||||
{
|
||||
// If the machine is CDZ head position is updated every 2 interrupts for audio tracks
|
||||
neocd.cdzIrq1Divisor ^= 1;
|
||||
}
|
||||
|
||||
if (!neocd.cdzIrq1Divisor)
|
||||
{
|
||||
// Update the read position
|
||||
neocd.cdrom.increasePosition();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the CDROM IRQ2, this is used to send commands packets / receive answer packets.
|
||||
if (neocd.isIRQ2Enabled())
|
||||
neocd.setInterrupt(NeoGeoCD::CdromCommunication);
|
||||
|
||||
// Update interrupts
|
||||
neocd.updateInterrupts();
|
||||
}
|
||||
|
||||
void audioCommandTimerCallback(Timer* timer, uint32_t userData)
|
||||
{
|
||||
uint32_t z80Elapsed;
|
||||
|
||||
// Post the audio command to the Z80
|
||||
neocd.audioCommand = userData;
|
||||
|
||||
// If the NMI is not disabled
|
||||
if (!neocd.z80NMIDisable)
|
||||
{
|
||||
// Trigger it
|
||||
z80_set_irq_line(INPUT_LINE_NMI, ASSERT_LINE);
|
||||
z80_set_irq_line(INPUT_LINE_NMI, CLEAR_LINE);
|
||||
|
||||
// Let the Z80 take the command into account
|
||||
z80Elapsed = Timer::z80ToMaster(z80_execute(Timer::masterToZ80(2000)));
|
||||
neocd.z80TimeSlice -= z80Elapsed;
|
||||
neocd.z80CyclesThisFrame += z80Elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
TimerGroup::TimerGroup() :
|
||||
watchdogTimer(nullptr),
|
||||
vblTimer(nullptr),
|
||||
hirqTimer(nullptr),
|
||||
drawlineTimer(nullptr),
|
||||
cdromIRQTimer(nullptr),
|
||||
ym2610TimerA(nullptr),
|
||||
ym2610TimerB(nullptr),
|
||||
audioCommandTimer(nullptr),
|
||||
m_timers()
|
||||
{
|
||||
m_timers.push_back(Timer());
|
||||
m_timers.push_back(Timer());
|
||||
m_timers.push_back(Timer());
|
||||
m_timers.push_back(Timer());
|
||||
m_timers.push_back(Timer());
|
||||
m_timers.push_back(Timer());
|
||||
m_timers.push_back(Timer());
|
||||
m_timers.push_back(Timer());
|
||||
m_timers.push_back(Timer());
|
||||
|
||||
int i = 0;
|
||||
|
||||
watchdogTimer = &m_timers[i++];
|
||||
watchdogTimer->setDelay(Timer::WATCHDOG_DELAY);
|
||||
watchdogTimer->setCallback(watchdogTimerCallback);
|
||||
|
||||
drawlineTimer = &m_timers[i++];
|
||||
drawlineTimer->setCallback(drawlineTimerCallback);
|
||||
|
||||
vblTimer = &m_timers[i++];
|
||||
vblTimer->setCallback(vblTimerCallback);
|
||||
|
||||
hirqTimer = &m_timers[i++];
|
||||
hirqTimer->setCallback(hirqTimerCallback);
|
||||
|
||||
cdromIRQTimer = &m_timers[i++];
|
||||
cdromIRQTimer->setCallback(cdromIRQTimerCallback);
|
||||
|
||||
audioCommandTimer = &m_timers[i++];
|
||||
audioCommandTimer->setCallback(audioCommandTimerCallback);
|
||||
|
||||
ym2610TimerA = &m_timers[i++];
|
||||
ym2610TimerA->setUserData(0);
|
||||
ym2610TimerA->setCallback(ym2610TimerCallback);
|
||||
|
||||
ym2610TimerB = &m_timers[i++];
|
||||
ym2610TimerB->setUserData(1);
|
||||
ym2610TimerB->setCallback(ym2610TimerCallback);
|
||||
}
|
||||
|
||||
void TimerGroup::reset()
|
||||
{
|
||||
watchdogTimer->setState(Timer::Stopped);
|
||||
|
||||
drawlineTimer->arm(Timer::pixelToMaster(Timer::HS_START));
|
||||
vblTimer->arm(Timer::pixelToMaster(Timer::SCREEN_WIDTH * Timer::VBLANK_LINE));
|
||||
|
||||
cdromIRQTimer->arm(neocd.isCDZ() ? (Timer::CDROM_DELAY / 2) : Timer::CDROM_DELAY);
|
||||
|
||||
hirqTimer->setState(Timer::Stopped);
|
||||
|
||||
audioCommandTimer->setState(Timer::Stopped);
|
||||
|
||||
ym2610TimerA->setState(Timer::Stopped);
|
||||
ym2610TimerB->setState(Timer::Stopped);
|
||||
}
|
||||
|
||||
int32_t TimerGroup::timeSlice() const
|
||||
{
|
||||
int32_t timeSlice = round<int32_t>(Timer::MASTER_CLOCK / Timer::FRAME_RATE);
|
||||
|
||||
for(const Timer& timer : m_timers)
|
||||
{
|
||||
if (timer.isActive())
|
||||
timeSlice = std::min(timeSlice, timer.delay());
|
||||
}
|
||||
|
||||
return timeSlice;
|
||||
}
|
||||
|
||||
void TimerGroup::advanceTime(const int32_t time)
|
||||
{
|
||||
for(Timer& timer : m_timers)
|
||||
timer.advanceTime(time);
|
||||
}
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const TimerGroup& timerGroup)
|
||||
{
|
||||
out << *timerGroup.watchdogTimer;
|
||||
out << *timerGroup.vblTimer;
|
||||
out << *timerGroup.hirqTimer;
|
||||
out << *timerGroup.drawlineTimer;
|
||||
out << *timerGroup.cdromIRQTimer;
|
||||
out << *timerGroup.ym2610TimerA;
|
||||
out << *timerGroup.ym2610TimerB;
|
||||
out << *timerGroup.audioCommandTimer;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
DataPacker& operator>>(DataPacker& in, TimerGroup& timerGroup)
|
||||
{
|
||||
in >> *timerGroup.watchdogTimer;
|
||||
in >> *timerGroup.vblTimer;
|
||||
in >> *timerGroup.hirqTimer;
|
||||
in >> *timerGroup.drawlineTimer;
|
||||
in >> *timerGroup.cdromIRQTimer;
|
||||
in >> *timerGroup.ym2610TimerA;
|
||||
in >> *timerGroup.ym2610TimerB;
|
||||
in >> *timerGroup.audioCommandTimer;
|
||||
|
||||
return in;
|
||||
}
|
45
src/timergroup.h
Normal file
45
src/timergroup.h
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef TIMERGROUP_H
|
||||
#define TIMERGROUP_H
|
||||
|
||||
#include "timer.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class TimerGroup
|
||||
{
|
||||
public:
|
||||
TimerGroup();
|
||||
|
||||
// Non copyable
|
||||
TimerGroup(const TimerGroup&) = delete;
|
||||
|
||||
// Non copyable
|
||||
TimerGroup& operator=(const TimerGroup&) = delete;
|
||||
|
||||
void reset();
|
||||
|
||||
int32_t timeSlice() const;
|
||||
|
||||
void advanceTime(const int32_t time);
|
||||
|
||||
Timer* watchdogTimer;
|
||||
Timer* vblTimer;
|
||||
Timer* hirqTimer;
|
||||
Timer* drawlineTimer;
|
||||
Timer* cdromIRQTimer;
|
||||
Timer* ym2610TimerA;
|
||||
Timer* ym2610TimerB;
|
||||
Timer* audioCommandTimer;
|
||||
|
||||
friend DataPacker& operator<<(DataPacker& out, const TimerGroup& timerGroup);
|
||||
friend DataPacker& operator>>(DataPacker& in, TimerGroup& timerGroup);
|
||||
|
||||
protected:
|
||||
std::vector<Timer> m_timers;
|
||||
};
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const TimerGroup& timerGroup);
|
||||
DataPacker& operator>>(DataPacker& in, TimerGroup& timerGroup);
|
||||
|
||||
#endif // TIMERGROUP_H
|
70
src/trackindex.h
Normal file
70
src/trackindex.h
Normal file
@ -0,0 +1,70 @@
|
||||
#ifndef TRACKINDEX
|
||||
#define TRACKINDEX
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class TrackIndex
|
||||
{
|
||||
public:
|
||||
TrackIndex() : m_trackIndex(0)
|
||||
{ }
|
||||
|
||||
TrackIndex(uint8_t track, uint8_t index) : m_trackIndex((uint16_t(track) << 8) | index)
|
||||
{ }
|
||||
|
||||
TrackIndex(uint16_t value) : m_trackIndex(value)
|
||||
{ }
|
||||
|
||||
TrackIndex(const TrackIndex& other) : m_trackIndex(other.m_trackIndex)
|
||||
{ }
|
||||
|
||||
uint8_t track() const
|
||||
{
|
||||
return m_trackIndex >> 8;
|
||||
}
|
||||
|
||||
void setTrack(uint8_t track)
|
||||
{
|
||||
m_trackIndex &= 0xFF;
|
||||
m_trackIndex |= uint16_t(track) << 8;
|
||||
}
|
||||
|
||||
uint8_t index() const
|
||||
{
|
||||
return m_trackIndex & 0xFF;
|
||||
}
|
||||
|
||||
void setIndex(uint8_t index)
|
||||
{
|
||||
m_trackIndex &= 0xFF00;
|
||||
m_trackIndex |= index;
|
||||
}
|
||||
|
||||
|
||||
bool operator==(const TrackIndex& other) const
|
||||
{
|
||||
return m_trackIndex == other.m_trackIndex;
|
||||
}
|
||||
|
||||
TrackIndex& operator=(const TrackIndex& other)
|
||||
{
|
||||
m_trackIndex = other.m_trackIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator<(const TrackIndex& other) const
|
||||
{
|
||||
return m_trackIndex < other.m_trackIndex;
|
||||
}
|
||||
|
||||
static uint16_t makeTrackIndex(uint8_t track, uint8_t index)
|
||||
{
|
||||
return (uint16_t(track) << 8) | index;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint16_t m_trackIndex;
|
||||
};
|
||||
|
||||
#endif // TRACKINDEX
|
||||
|
930
src/video.cpp
Normal file
930
src/video.cpp
Normal file
@ -0,0 +1,930 @@
|
||||
#include "neogeocd.h"
|
||||
#include "video.h"
|
||||
#include "endian.h"
|
||||
#include "always_inline.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
static const uint32_t SPR_DECODE_TABLE[256] =
|
||||
{
|
||||
0x00000000, 0x00000001, 0x00000010, 0x00000011,
|
||||
0x00000100, 0x00000101, 0x00000110, 0x00000111,
|
||||
0x00001000, 0x00001001, 0x00001010, 0x00001011,
|
||||
0x00001100, 0x00001101, 0x00001110, 0x00001111,
|
||||
0x00010000, 0x00010001, 0x00010010, 0x00010011,
|
||||
0x00010100, 0x00010101, 0x00010110, 0x00010111,
|
||||
0x00011000, 0x00011001, 0x00011010, 0x00011011,
|
||||
0x00011100, 0x00011101, 0x00011110, 0x00011111,
|
||||
0x00100000, 0x00100001, 0x00100010, 0x00100011,
|
||||
0x00100100, 0x00100101, 0x00100110, 0x00100111,
|
||||
0x00101000, 0x00101001, 0x00101010, 0x00101011,
|
||||
0x00101100, 0x00101101, 0x00101110, 0x00101111,
|
||||
0x00110000, 0x00110001, 0x00110010, 0x00110011,
|
||||
0x00110100, 0x00110101, 0x00110110, 0x00110111,
|
||||
0x00111000, 0x00111001, 0x00111010, 0x00111011,
|
||||
0x00111100, 0x00111101, 0x00111110, 0x00111111,
|
||||
0x01000000, 0x01000001, 0x01000010, 0x01000011,
|
||||
0x01000100, 0x01000101, 0x01000110, 0x01000111,
|
||||
0x01001000, 0x01001001, 0x01001010, 0x01001011,
|
||||
0x01001100, 0x01001101, 0x01001110, 0x01001111,
|
||||
0x01010000, 0x01010001, 0x01010010, 0x01010011,
|
||||
0x01010100, 0x01010101, 0x01010110, 0x01010111,
|
||||
0x01011000, 0x01011001, 0x01011010, 0x01011011,
|
||||
0x01011100, 0x01011101, 0x01011110, 0x01011111,
|
||||
0x01100000, 0x01100001, 0x01100010, 0x01100011,
|
||||
0x01100100, 0x01100101, 0x01100110, 0x01100111,
|
||||
0x01101000, 0x01101001, 0x01101010, 0x01101011,
|
||||
0x01101100, 0x01101101, 0x01101110, 0x01101111,
|
||||
0x01110000, 0x01110001, 0x01110010, 0x01110011,
|
||||
0x01110100, 0x01110101, 0x01110110, 0x01110111,
|
||||
0x01111000, 0x01111001, 0x01111010, 0x01111011,
|
||||
0x01111100, 0x01111101, 0x01111110, 0x01111111,
|
||||
0x10000000, 0x10000001, 0x10000010, 0x10000011,
|
||||
0x10000100, 0x10000101, 0x10000110, 0x10000111,
|
||||
0x10001000, 0x10001001, 0x10001010, 0x10001011,
|
||||
0x10001100, 0x10001101, 0x10001110, 0x10001111,
|
||||
0x10010000, 0x10010001, 0x10010010, 0x10010011,
|
||||
0x10010100, 0x10010101, 0x10010110, 0x10010111,
|
||||
0x10011000, 0x10011001, 0x10011010, 0x10011011,
|
||||
0x10011100, 0x10011101, 0x10011110, 0x10011111,
|
||||
0x10100000, 0x10100001, 0x10100010, 0x10100011,
|
||||
0x10100100, 0x10100101, 0x10100110, 0x10100111,
|
||||
0x10101000, 0x10101001, 0x10101010, 0x10101011,
|
||||
0x10101100, 0x10101101, 0x10101110, 0x10101111,
|
||||
0x10110000, 0x10110001, 0x10110010, 0x10110011,
|
||||
0x10110100, 0x10110101, 0x10110110, 0x10110111,
|
||||
0x10111000, 0x10111001, 0x10111010, 0x10111011,
|
||||
0x10111100, 0x10111101, 0x10111110, 0x10111111,
|
||||
0x11000000, 0x11000001, 0x11000010, 0x11000011,
|
||||
0x11000100, 0x11000101, 0x11000110, 0x11000111,
|
||||
0x11001000, 0x11001001, 0x11001010, 0x11001011,
|
||||
0x11001100, 0x11001101, 0x11001110, 0x11001111,
|
||||
0x11010000, 0x11010001, 0x11010010, 0x11010011,
|
||||
0x11010100, 0x11010101, 0x11010110, 0x11010111,
|
||||
0x11011000, 0x11011001, 0x11011010, 0x11011011,
|
||||
0x11011100, 0x11011101, 0x11011110, 0x11011111,
|
||||
0x11100000, 0x11100001, 0x11100010, 0x11100011,
|
||||
0x11100100, 0x11100101, 0x11100110, 0x11100111,
|
||||
0x11101000, 0x11101001, 0x11101010, 0x11101011,
|
||||
0x11101100, 0x11101101, 0x11101110, 0x11101111,
|
||||
0x11110000, 0x11110001, 0x11110010, 0x11110011,
|
||||
0x11110100, 0x11110101, 0x11110110, 0x11110111,
|
||||
0x11111000, 0x11111001, 0x11111010, 0x11111011,
|
||||
0x11111100, 0x11111101, 0x11111110, 0x11111111
|
||||
};
|
||||
|
||||
Video::Video() :
|
||||
paletteRamPc(nullptr),
|
||||
fixUsageMap(nullptr),
|
||||
frameBuffer(nullptr),
|
||||
activePaletteBank(0),
|
||||
autoAnimationCounter(0),
|
||||
autoAnimationFrameCounter(0),
|
||||
autoAnimationSpeed(0),
|
||||
autoAnimationDisabled(false),
|
||||
sprDisable(true),
|
||||
fixDisable(true),
|
||||
videoEnable(false),
|
||||
hirqControl(HIRQ_CTRL_DISABLE),
|
||||
hirqRegister(0),
|
||||
videoramOffset(0),
|
||||
videoramModulo(0),
|
||||
videoramData(0),
|
||||
sprite_x(0),
|
||||
sprite_y(0),
|
||||
sprite_zoomX(15),
|
||||
sprite_zoomY(255),
|
||||
sprite_clipping(0x20)
|
||||
{
|
||||
// Palette RAM: 16KiB, converted into RGB565 equivalent
|
||||
paletteRamPc = reinterpret_cast<uint16_t*>(std::malloc(Memory::PALETTERAM_SIZE));
|
||||
|
||||
// Fix usage map, if zero tile is fully transparent
|
||||
fixUsageMap = reinterpret_cast<uint8_t*>(std::malloc(Memory::FIXRAM_SIZE / 32));
|
||||
|
||||
// 304x224 RGB565 framebuffer
|
||||
frameBuffer = reinterpret_cast<uint16_t*>(std::malloc(FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT * sizeof(uint16_t)));
|
||||
}
|
||||
|
||||
Video::~Video()
|
||||
{
|
||||
if (frameBuffer)
|
||||
std::free(frameBuffer);
|
||||
|
||||
if (fixUsageMap)
|
||||
std::free(fixUsageMap);
|
||||
|
||||
if (paletteRamPc)
|
||||
std::free(paletteRamPc);
|
||||
}
|
||||
|
||||
void Video::reset()
|
||||
{
|
||||
std::memset(paletteRamPc, 0, Memory::PALETTERAM_SIZE);
|
||||
std::memset(fixUsageMap, 0, Memory::FIXRAM_SIZE / 32);
|
||||
activePaletteBank = 0;
|
||||
autoAnimationCounter = 0;
|
||||
autoAnimationFrameCounter = 0;
|
||||
autoAnimationSpeed = 0;
|
||||
autoAnimationDisabled = false;
|
||||
sprDisable = true;
|
||||
fixDisable = true;
|
||||
videoEnable = false;
|
||||
hirqControl = HIRQ_CTRL_DISABLE;
|
||||
hirqRegister = 0;
|
||||
videoramOffset = 0;
|
||||
videoramModulo = 0;
|
||||
videoramData = 0;
|
||||
sprite_x = 0;
|
||||
sprite_y = 0;
|
||||
sprite_zoomX = 15;
|
||||
sprite_zoomY = 255;
|
||||
sprite_clipping = 0x20;
|
||||
}
|
||||
|
||||
void Video::convertColor(uint32_t index)
|
||||
{
|
||||
uint16_t c = BIG_ENDIAN_WORD(neocd.memory.paletteRam[index]);
|
||||
|
||||
paletteRamPc[index] = ((c & 0x0F00) << 4) | ((c & 0x4000) >> 3) |
|
||||
((c & 0x00F0) << 3) | ((c & 0x2000) >> 7) |
|
||||
((c & 0x000F) << 1) | ((c & 0x1000) >> 12);
|
||||
}
|
||||
|
||||
void Video::convertPalette()
|
||||
{
|
||||
for (int i = 0; i < (Memory::PALETTERAM_SIZE / sizeof(uint16_t)); ++i)
|
||||
convertColor(i);
|
||||
}
|
||||
|
||||
void Video::updateFixUsageMap()
|
||||
{
|
||||
uint8_t* fixPtr = neocd.memory.fixRam;
|
||||
uint8_t* usagePtrStart = fixUsageMap;
|
||||
uint8_t* usagePtrEnd = fixUsageMap + (Memory::FIXRAM_SIZE / 32);
|
||||
|
||||
std::generate(usagePtrStart, usagePtrEnd, [&]() {
|
||||
bool result = std::any_of(fixPtr, fixPtr + 32, [](const uint8_t& value) {
|
||||
return value ? 1 : 0;
|
||||
});
|
||||
|
||||
fixPtr += 32;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
// Note: scanline between 16 and 240!
|
||||
void Video::drawFix(uint32_t scanline)
|
||||
{
|
||||
// Skip the first column (framebuffer width is 304 instead of 320)
|
||||
uint16_t* videoRamPtr = &neocd.memory.videoRam[(0xE004 / 2) + ((scanline - 16) / 8)] + 32;
|
||||
uint16_t* videoRamEndPtr = videoRamPtr + (FRAMEBUFFER_WIDTH * 4);
|
||||
uint16_t* frameBufferPtr = frameBuffer + ((scanline - 16) * FRAMEBUFFER_WIDTH);
|
||||
|
||||
for (; videoRamPtr < videoRamEndPtr; videoRamPtr += 32)
|
||||
{
|
||||
int character = (*videoRamPtr) & 0x0FFF;
|
||||
int palette = ((*videoRamPtr) & 0xF000) >> 12;
|
||||
|
||||
// Check for total transparency, no need to draw
|
||||
if (!fixUsageMap[character])
|
||||
{
|
||||
frameBufferPtr += 8;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t* fixBase = &neocd.memory.fixRam[(character * 32) + (scanline % 8)];
|
||||
uint16_t* paletteBase = &paletteRamPc[(activePaletteBank * 4096) + (palette * 16)];
|
||||
|
||||
auto decodeAndDrawFix = [&](int n) ALWAYS_INLINE {
|
||||
uint8_t pixelA = fixBase[n];
|
||||
uint8_t pixelB = pixelA >> 4;
|
||||
pixelA &= 0x0F;
|
||||
|
||||
if (pixelA)
|
||||
*frameBufferPtr = paletteBase[pixelA];
|
||||
frameBufferPtr++;
|
||||
|
||||
if (pixelB)
|
||||
*frameBufferPtr = paletteBase[pixelB];
|
||||
frameBufferPtr++;
|
||||
};
|
||||
|
||||
decodeAndDrawFix(16);
|
||||
decodeAndDrawFix(24);
|
||||
decodeAndDrawFix(0);
|
||||
decodeAndDrawFix(8);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool isSpriteOnScanline(uint32_t scanline, uint32_t y, uint32_t clipping)
|
||||
{
|
||||
return (clipping == 0) || (clipping >= 0x20) || ((scanline - y) & 0x1ff) < (clipping * 0x10);
|
||||
}
|
||||
|
||||
uint16_t Video::createSpriteList(uint32_t scanline, uint16_t *spriteList) const
|
||||
{
|
||||
uint16_t* attributesPtr = &neocd.memory.videoRam[0x8200];
|
||||
uint16_t activeCount = 0;
|
||||
uint16_t attributes;
|
||||
uint32_t y = sprite_y;
|
||||
uint32_t clipping = sprite_clipping;
|
||||
bool spriteIsOnScanline = false;
|
||||
|
||||
for (uint16_t spriteNumber = 0; spriteNumber < MAX_SPRITES_PER_SCREEN; ++spriteNumber)
|
||||
{
|
||||
attributes = *attributesPtr++;
|
||||
|
||||
if (!(attributes & 0x40))
|
||||
{
|
||||
y = 0x200 - (attributes >> 7);
|
||||
clipping = attributes & 0x3F;
|
||||
spriteIsOnScanline = isSpriteOnScanline(scanline, y, clipping);
|
||||
}
|
||||
|
||||
if (!spriteIsOnScanline)
|
||||
continue;
|
||||
|
||||
if (!clipping)
|
||||
continue;
|
||||
|
||||
*spriteList++ = spriteNumber;
|
||||
activeCount++;
|
||||
|
||||
if (activeCount >= MAX_SPRITES_PER_LINE)
|
||||
break;
|
||||
}
|
||||
|
||||
// Fill the rest of the sprite list with 0, including one extra entry
|
||||
std::memset(spriteList, 0, sizeof(uint16_t) * (MAX_SPRITES_PER_LINE - activeCount + 1));
|
||||
|
||||
return activeCount;
|
||||
}
|
||||
|
||||
void Video::drawSprites(uint32_t scanline, uint16_t *spriteList, uint16_t spritesToDraw)
|
||||
{
|
||||
for (uint16_t currentSprite = 0; currentSprite <= spritesToDraw; currentSprite++)
|
||||
{
|
||||
uint16_t spriteNumber = *spriteList++;
|
||||
// Optimization: No need to draw sprite zero now if we're going to draw it again in the end
|
||||
if ((!spriteNumber) && (currentSprite < spritesToDraw))
|
||||
continue;
|
||||
|
||||
uint16_t spriteAttributes1 = neocd.memory.videoRam[0x8000 + spriteNumber];
|
||||
uint16_t spriteAttributes2 = neocd.memory.videoRam[0x8200 + spriteNumber];
|
||||
uint16_t spriteAttributes3 = neocd.memory.videoRam[0x8400 + spriteNumber];
|
||||
|
||||
if (spriteAttributes2 & 0x40)
|
||||
{
|
||||
sprite_x = (sprite_x + sprite_zoomX + 1) & 0x1FF;
|
||||
sprite_zoomX = (spriteAttributes1 >> 8) & 0xF;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite_zoomY = spriteAttributes1 & 0xFF;
|
||||
sprite_zoomX = (spriteAttributes1 >> 8) & 0xF;
|
||||
sprite_clipping = spriteAttributes2 & 0x3F;
|
||||
sprite_y = 0x200 - (spriteAttributes2 >> 7);
|
||||
sprite_x = spriteAttributes3 >> 7;
|
||||
}
|
||||
|
||||
if ((sprite_x >= 0x138) && (sprite_x <= 0x1F8))
|
||||
continue;
|
||||
|
||||
drawSprite(spriteNumber, sprite_x, sprite_y, sprite_zoomX, sprite_zoomY, scanline, sprite_clipping);
|
||||
}
|
||||
}
|
||||
|
||||
inline void drawSpriteLine(
|
||||
uint32_t zoomX,
|
||||
int increment,
|
||||
uint32_t pixelData,
|
||||
uint32_t pixelDataB,
|
||||
const uint16_t* paletteBase,
|
||||
uint16_t*& frameBufferPtr)
|
||||
{
|
||||
auto sprShift = [&](int n) {
|
||||
pixelData >>= (n * 4);
|
||||
};
|
||||
|
||||
auto sprDrawPixel = [&]() {
|
||||
uint8_t pixel = pixelData & 0xF;
|
||||
if (pixel)
|
||||
*frameBufferPtr = paletteBase[pixel];
|
||||
frameBufferPtr += increment;
|
||||
};
|
||||
|
||||
switch (zoomX)
|
||||
{
|
||||
case 0: // 00000000
|
||||
break;
|
||||
|
||||
case 1: // 00001000
|
||||
case 2:
|
||||
sprShift(4);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 3: // 00101000
|
||||
case 4:
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 5: // 00101010
|
||||
case 6:
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 7: // 10101010
|
||||
case 8:
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 9: // 10111010
|
||||
case 10:
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 11: // 10111011
|
||||
case 12:
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 13: // 11111011
|
||||
case 14:
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 15: // 11111111
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
}
|
||||
|
||||
pixelData = pixelDataB;
|
||||
|
||||
switch (zoomX)
|
||||
{
|
||||
case 0: // 10000000
|
||||
case 1:
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 2: // 10001000
|
||||
case 3:
|
||||
sprDrawPixel();
|
||||
sprShift(4);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 4: // 10001010
|
||||
case 5:
|
||||
sprDrawPixel();
|
||||
sprShift(4);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 6: // 10101010
|
||||
case 7:
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 8: // 11101010
|
||||
case 9:
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 10: // 11101011
|
||||
case 11:
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 12: // 11101111
|
||||
case 13:
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 14:
|
||||
case 15: // 11111111
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void drawSpriteLineClipped(
|
||||
uint32_t zoomX,
|
||||
int increment,
|
||||
uint32_t pixelData,
|
||||
uint32_t pixelDataB,
|
||||
const uint16_t* paletteBase,
|
||||
uint16_t*& frameBufferPtr,
|
||||
const uint16_t* low,
|
||||
const uint16_t* high)
|
||||
{
|
||||
auto sprShift = [&](int n) {
|
||||
pixelData >>= (n * 4);
|
||||
};
|
||||
|
||||
auto sprDrawPixel = [&]() {
|
||||
uint8_t pixel = pixelData & 0xF;
|
||||
if (pixel && (frameBufferPtr >= low) && (frameBufferPtr < high))
|
||||
*frameBufferPtr = paletteBase[pixel];
|
||||
frameBufferPtr += increment;
|
||||
};
|
||||
|
||||
switch (zoomX)
|
||||
{
|
||||
case 0: // 00000000
|
||||
break;
|
||||
|
||||
case 1: // 00001000
|
||||
case 2:
|
||||
sprShift(4);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 3: // 00101000
|
||||
case 4:
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 5: // 00101010
|
||||
case 6:
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 7: // 10101010
|
||||
case 8:
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 9: // 10111010
|
||||
case 10:
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 11: // 10111011
|
||||
case 12:
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 13: // 11111011
|
||||
case 14:
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 15: // 11111111
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
}
|
||||
|
||||
pixelData = pixelDataB;
|
||||
|
||||
switch (zoomX)
|
||||
{
|
||||
case 0: // 10000000
|
||||
case 1:
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 2: // 10001000
|
||||
case 3:
|
||||
sprDrawPixel();
|
||||
sprShift(4);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 4: // 10001010
|
||||
case 5:
|
||||
sprDrawPixel();
|
||||
sprShift(4);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 6: // 10101010
|
||||
case 7:
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 8: // 11101010
|
||||
case 9:
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 10: // 11101011
|
||||
case 11:
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 12: // 11101111
|
||||
case 13:
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(2);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
|
||||
case 14:
|
||||
case 15: // 11111111
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
sprShift(1);
|
||||
sprDrawPixel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Video::drawSprite(uint32_t spriteNumber, uint32_t x, uint32_t y, uint32_t zoomX, uint32_t zoomY, uint32_t scanline, uint32_t clipping)
|
||||
{
|
||||
uint32_t spriteLine = (scanline - y) & 0x1FF;
|
||||
uint32_t zoomLine = spriteLine & 0xFF;
|
||||
bool invert = (spriteLine & 0x100) != 0;
|
||||
bool clippedDrawing;
|
||||
|
||||
if (x >= 0x1F9)
|
||||
{
|
||||
if (x + zoomX + 1 > 0x208)
|
||||
clippedDrawing = true;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else if (x < 8)
|
||||
{
|
||||
if (x + zoomX + 1 > 8)
|
||||
clippedDrawing = true;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else if (x + zoomX + 1 > 0x138)
|
||||
clippedDrawing = true;
|
||||
else
|
||||
clippedDrawing = false;
|
||||
|
||||
if (invert)
|
||||
zoomLine ^= 0xFF;
|
||||
|
||||
if (clipping > 0x20)
|
||||
{
|
||||
zoomLine = zoomLine % ((zoomY + 1) << 1);
|
||||
|
||||
if (zoomLine > zoomY)
|
||||
{
|
||||
zoomLine = ((zoomY + 1) << 1) - 1 - zoomLine;
|
||||
invert = !invert;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t tileNumber = neocd.memory.yZoomRom[zoomY * 256 + zoomLine];
|
||||
uint32_t tileLine = tileNumber & 0xF;
|
||||
tileNumber >>= 4;
|
||||
|
||||
if (invert)
|
||||
{
|
||||
tileLine ^= 0x0f;
|
||||
tileNumber ^= 0x1f;
|
||||
}
|
||||
|
||||
uint32_t tileIndex = neocd.memory.videoRam[spriteNumber * 64 + tileNumber * 2];
|
||||
uint32_t tileControl = neocd.memory.videoRam[spriteNumber * 64 + tileNumber * 2 + 1];
|
||||
|
||||
if (tileControl & 2)
|
||||
tileLine ^= 0x0F;
|
||||
|
||||
// Auto animation
|
||||
if (!autoAnimationDisabled)
|
||||
{
|
||||
if (tileControl & 0x0008)
|
||||
tileIndex = (tileIndex & ~0x07) | (autoAnimationCounter & 0x07);
|
||||
else if (tileControl & 0x0004)
|
||||
tileIndex = (tileIndex & ~0x03) | (autoAnimationCounter & 0x03);
|
||||
}
|
||||
|
||||
const uint16_t* paletteBase = &paletteRamPc[(activePaletteBank * 0x1000) + ((tileControl >> 8) * 16)];
|
||||
const uint8_t* spriteBase = &neocd.memory.sprRam[(tileIndex & 0x7FFF) * 128 + (tileLine * 4)];
|
||||
|
||||
uint16_t* frameBufferPtr = frameBuffer;
|
||||
|
||||
if (x > 0x1F0)
|
||||
{
|
||||
frameBufferPtr += x;
|
||||
frameBufferPtr -= 0x208;
|
||||
}
|
||||
else
|
||||
{
|
||||
frameBufferPtr += x;
|
||||
frameBufferPtr -= 8;
|
||||
}
|
||||
|
||||
frameBufferPtr += (scanline - 16) * FRAMEBUFFER_WIDTH;
|
||||
|
||||
int increment;
|
||||
|
||||
if (tileControl & 1)
|
||||
{
|
||||
frameBufferPtr += zoomX;
|
||||
increment = -1;
|
||||
}
|
||||
else
|
||||
increment = 1;
|
||||
|
||||
uint32_t pixelData = SPR_DECODE_TABLE[spriteBase[64 + 1]]
|
||||
| (SPR_DECODE_TABLE[spriteBase[64 + 0]] << 1)
|
||||
| (SPR_DECODE_TABLE[spriteBase[64 + 3]] << 2)
|
||||
| (SPR_DECODE_TABLE[spriteBase[64 + 2]] << 3);
|
||||
|
||||
uint32_t pixelDataB = SPR_DECODE_TABLE[spriteBase[0 + 1]]
|
||||
| (SPR_DECODE_TABLE[spriteBase[0 + 0]] << 1)
|
||||
| (SPR_DECODE_TABLE[spriteBase[0 + 3]] << 2)
|
||||
| (SPR_DECODE_TABLE[spriteBase[0 + 2]] << 3);
|
||||
|
||||
if (clippedDrawing)
|
||||
{
|
||||
drawSpriteLineClipped(
|
||||
zoomX,
|
||||
increment,
|
||||
pixelData,
|
||||
pixelDataB,
|
||||
paletteBase,
|
||||
frameBufferPtr,
|
||||
frameBuffer + ((scanline - 16) * FRAMEBUFFER_WIDTH),
|
||||
frameBuffer + ((scanline - 15) * FRAMEBUFFER_WIDTH));
|
||||
}
|
||||
else
|
||||
drawSpriteLine(zoomX, increment, pixelData, pixelDataB, paletteBase, frameBufferPtr);
|
||||
}
|
||||
|
||||
void Video::drawBlackLine(uint32_t scanline)
|
||||
{
|
||||
std::memset(&frameBuffer[(scanline - 16) * Video::FRAMEBUFFER_WIDTH], 0, Video::FRAMEBUFFER_WIDTH * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
void Video::drawEmptyLine(uint32_t scanline)
|
||||
{
|
||||
uint16_t* ptr = &frameBuffer[(scanline - 16) * Video::FRAMEBUFFER_WIDTH];
|
||||
uint16_t* ptrEnd = ptr + Video::FRAMEBUFFER_WIDTH;
|
||||
uint16_t color = paletteRamPc[(activePaletteBank * 0x1000) + 4095];
|
||||
|
||||
std::fill(ptr, ptrEnd, color);
|
||||
}
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const Video& video)
|
||||
{
|
||||
out << video.activePaletteBank;
|
||||
out << video.autoAnimationCounter;
|
||||
out << video.autoAnimationSpeed;
|
||||
out << video.autoAnimationFrameCounter;
|
||||
out << video.autoAnimationDisabled;
|
||||
out << video.sprDisable;
|
||||
out << video.fixDisable;
|
||||
out << video.videoEnable;
|
||||
out << video.hirqControl;
|
||||
out << video.hirqRegister;
|
||||
out << video.videoramOffset;
|
||||
out << video.videoramModulo;
|
||||
out << video.videoramData;
|
||||
out << video.sprite_x;
|
||||
out << video.sprite_y;
|
||||
out << video.sprite_zoomX;
|
||||
out << video.sprite_zoomY;
|
||||
out << video.sprite_clipping;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
DataPacker& operator>>(DataPacker& in, Video& video)
|
||||
{
|
||||
in >> video.activePaletteBank;
|
||||
in >> video.autoAnimationCounter;
|
||||
in >> video.autoAnimationSpeed;
|
||||
in >> video.autoAnimationFrameCounter;
|
||||
in >> video.autoAnimationDisabled;
|
||||
in >> video.sprDisable;
|
||||
in >> video.fixDisable;
|
||||
in >> video.videoEnable;
|
||||
in >> video.hirqControl;
|
||||
in >> video.hirqRegister;
|
||||
in >> video.videoramOffset;
|
||||
in >> video.videoramModulo;
|
||||
in >> video.videoramData;
|
||||
in >> video.sprite_x;
|
||||
in >> video.sprite_y;
|
||||
in >> video.sprite_zoomX;
|
||||
in >> video.sprite_zoomY;
|
||||
in >> video.sprite_clipping;
|
||||
|
||||
return in;
|
||||
}
|
88
src/video.h
Normal file
88
src/video.h
Normal file
@ -0,0 +1,88 @@
|
||||
#ifndef VIDEO_H
|
||||
#define VIDEO_H
|
||||
|
||||
#include "datapacker.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Video
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t FRAMEBUFFER_WIDTH = 304;
|
||||
static constexpr uint32_t FRAMEBUFFER_HEIGHT = 224;
|
||||
static constexpr float ASPECT_RATIO = 4.0f / 3.0f;
|
||||
static constexpr uint16_t MAX_SPRITES_PER_SCREEN = 381;
|
||||
static constexpr uint16_t MAX_SPRITES_PER_LINE = 96;
|
||||
|
||||
enum HirqControl
|
||||
{
|
||||
HIRQ_CTRL_DISABLE = 0x00,
|
||||
HIRQ_CTRL_ENABLE = 0x10,
|
||||
HIRQ_CTRL_RELATIVE = 0x20,
|
||||
HIRQ_CTRL_VBLANK_LOAD = 0x40,
|
||||
HIRQ_CTRL_AUTOREPEAT = 0x80
|
||||
};
|
||||
|
||||
Video();
|
||||
~Video();
|
||||
|
||||
// Non copyable
|
||||
Video(const Video&) = delete;
|
||||
|
||||
// Non copyable
|
||||
Video& operator=(const Video&) = delete;
|
||||
|
||||
void reset();
|
||||
|
||||
void convertColor(uint32_t index);
|
||||
void convertPalette();
|
||||
|
||||
void updateFixUsageMap();
|
||||
|
||||
void drawFix(uint32_t scanline);
|
||||
|
||||
uint16_t createSpriteList(uint32_t scanline, uint16_t *spriteList) const;
|
||||
void drawSprites(uint32_t scanline, uint16_t *spriteList, uint16_t spritesToDraw);
|
||||
void drawSprite(uint32_t spriteNumber,
|
||||
uint32_t x,
|
||||
uint32_t y,
|
||||
uint32_t zoomX,
|
||||
uint32_t zoomY,
|
||||
uint32_t scanline,
|
||||
uint32_t clipping);
|
||||
void drawBlackLine(uint32_t scanline);
|
||||
void drawEmptyLine(uint32_t scanline);
|
||||
|
||||
friend DataPacker& operator<<(DataPacker& out, const Video& video);
|
||||
friend DataPacker& operator>>(DataPacker& in, Video& video);
|
||||
|
||||
uint16_t* paletteRamPc;
|
||||
uint8_t* fixUsageMap;
|
||||
uint16_t* frameBuffer;
|
||||
|
||||
// Variables to save in savestate
|
||||
uint32_t activePaletteBank;
|
||||
uint32_t autoAnimationCounter;
|
||||
uint32_t autoAnimationSpeed;
|
||||
uint32_t autoAnimationFrameCounter;
|
||||
bool autoAnimationDisabled;
|
||||
bool sprDisable;
|
||||
bool fixDisable;
|
||||
bool videoEnable;
|
||||
uint32_t hirqControl;
|
||||
uint32_t hirqRegister;
|
||||
uint32_t videoramOffset;
|
||||
uint32_t videoramModulo;
|
||||
uint32_t videoramData;
|
||||
uint32_t sprite_x;
|
||||
uint32_t sprite_y;
|
||||
uint32_t sprite_zoomX;
|
||||
uint32_t sprite_zoomY;
|
||||
uint32_t sprite_clipping;
|
||||
// End variables to save in savestate
|
||||
};
|
||||
|
||||
DataPacker& operator<<(DataPacker& out, const Video& video);
|
||||
DataPacker& operator>>(DataPacker& in, Video& video);
|
||||
|
||||
#endif // VIDEO_H
|
168
src/wavfile.cpp
Normal file
168
src/wavfile.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
#include "wavfile.h"
|
||||
#include "packedstruct.h"
|
||||
#include "endian.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdint.h>
|
||||
#include <cassert>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(push,1)
|
||||
#endif
|
||||
|
||||
typedef struct PACKED {
|
||||
uint32_t magic;
|
||||
uint32_t fileSize;
|
||||
uint32_t formatId;
|
||||
} wave_riff_header_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint16_t audioFormat;
|
||||
uint16_t channelCount;
|
||||
uint32_t sampleRate;
|
||||
uint32_t bytesPerSecond;
|
||||
uint16_t bytesPerBlock;
|
||||
uint16_t bitsPerSample;
|
||||
} wave_fmt_header_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint32_t magic;
|
||||
uint32_t dataSize;
|
||||
} wave_chunk_header_t;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
WavFile::WavFile() :
|
||||
m_file(nullptr),
|
||||
m_currentPosition(0),
|
||||
m_dataStart(0),
|
||||
m_dataSize(0)
|
||||
{
|
||||
assert(sizeof(wave_riff_header_t) == 12);
|
||||
assert(sizeof(wave_fmt_header_t) == 16);
|
||||
assert(sizeof(wave_chunk_header_t) == 8);
|
||||
}
|
||||
|
||||
WavFile::~WavFile()
|
||||
{
|
||||
}
|
||||
|
||||
bool WavFile::initialize(std::ifstream *file)
|
||||
{
|
||||
cleanup();
|
||||
|
||||
m_file = file;
|
||||
|
||||
if (!m_file->is_open())
|
||||
return false;
|
||||
|
||||
wave_riff_header_t waveHeader;
|
||||
wave_chunk_header_t chunkHeader;
|
||||
wave_fmt_header_t fmtHeader;
|
||||
|
||||
// First read in the header
|
||||
file->read(reinterpret_cast<char*>(&waveHeader), sizeof(waveHeader));
|
||||
if (file->gcount() < sizeof(waveHeader))
|
||||
return false;
|
||||
|
||||
// ... and check that indeed it is a WAVE file
|
||||
if ((waveHeader.magic != LITTLE_ENDIAN_DWORD(0x46464952)) || (waveHeader.formatId != LITTLE_ENDIAN_DWORD(0x45564157)))
|
||||
return false;
|
||||
|
||||
size_t fileSize = LITTLE_ENDIAN_DWORD(waveHeader.fileSize) + 8;
|
||||
size_t fmtHeaderPos = 0;
|
||||
size_t dataPos = 0;
|
||||
size_t dataSize = 0;
|
||||
|
||||
// Next, scan all chunks in the WAVE file looking for two specific chunks: fmt and data
|
||||
do
|
||||
{
|
||||
file->read(reinterpret_cast<char*>(&chunkHeader), sizeof(chunkHeader));
|
||||
if (file->gcount() < sizeof(chunkHeader))
|
||||
return false;
|
||||
|
||||
std::ios::pos_type currentPosition = file->tellg();
|
||||
if (currentPosition < 0)
|
||||
return false;
|
||||
|
||||
if (static_cast<size_t>(currentPosition) + LITTLE_ENDIAN_DWORD(chunkHeader.dataSize) > fileSize)
|
||||
return false;
|
||||
|
||||
if (chunkHeader.magic == LITTLE_ENDIAN_DWORD(0x20746d66))
|
||||
fmtHeaderPos = currentPosition;
|
||||
else if (chunkHeader.magic == LITTLE_ENDIAN_DWORD(0x61746164))
|
||||
{
|
||||
dataPos = currentPosition;
|
||||
dataSize = LITTLE_ENDIAN_DWORD(chunkHeader.dataSize);
|
||||
}
|
||||
|
||||
file->seekg(LITTLE_ENDIAN_DWORD(chunkHeader.dataSize), std::ios::cur);
|
||||
} while((dataPos == 0) || (fmtHeaderPos == 0));
|
||||
|
||||
// Read in the fmt chunk
|
||||
file->seekg(fmtHeaderPos, std::ios::beg);
|
||||
file->read(reinterpret_cast<char*>(&fmtHeader), sizeof(fmtHeader));
|
||||
if (file->gcount() < sizeof(fmtHeader))
|
||||
return false;
|
||||
|
||||
// ... and check that the audio has the desired format
|
||||
if ((fmtHeader.audioFormat != LITTLE_ENDIAN_WORD(1))
|
||||
|| (fmtHeader.bitsPerSample != LITTLE_ENDIAN_WORD(16))
|
||||
|| (fmtHeader.sampleRate != LITTLE_ENDIAN_DWORD(44100)))
|
||||
return false;
|
||||
|
||||
// All ok!
|
||||
m_currentPosition = 0;
|
||||
m_dataStart = dataPos;
|
||||
m_dataSize = dataSize;
|
||||
|
||||
file->seekg(m_dataStart, std::ios::beg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t WavFile::read(char *data, size_t size)
|
||||
{
|
||||
if ((!m_file) || (m_dataSize <= 0))
|
||||
return 0;
|
||||
|
||||
size_t available = m_dataSize - m_currentPosition;
|
||||
size_t slice = std::min(size, available);
|
||||
|
||||
m_file->read(data, slice);
|
||||
|
||||
// Fix ifstream stupidity: eof is not a failure condition
|
||||
if (m_file->fail() && m_file->eof())
|
||||
m_file->clear();
|
||||
|
||||
size_t done = static_cast<size_t>(m_file->gcount());
|
||||
m_currentPosition += done;
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
bool WavFile::seek(size_t position)
|
||||
{
|
||||
m_currentPosition = std::min(position, m_dataSize);
|
||||
|
||||
m_file->seekg(m_currentPosition + m_dataStart, std::ios::beg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t WavFile::length()
|
||||
{
|
||||
if ((!m_file) || (m_dataSize <= 0))
|
||||
return 0;
|
||||
|
||||
return m_dataSize;
|
||||
}
|
||||
|
||||
void WavFile::cleanup()
|
||||
{
|
||||
m_currentPosition = 0;
|
||||
m_dataStart = 0;
|
||||
m_dataSize = 0;
|
||||
}
|
35
src/wavfile.h
Normal file
35
src/wavfile.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef WAVFILE_H
|
||||
#define WAVFILE_H
|
||||
|
||||
#include <fstream>
|
||||
|
||||
class WavFile
|
||||
{
|
||||
public:
|
||||
WavFile();
|
||||
~WavFile();
|
||||
|
||||
// Non copyable
|
||||
WavFile(const WavFile&) = delete;
|
||||
|
||||
// Non copyable
|
||||
WavFile& operator=(const WavFile&) = delete;
|
||||
|
||||
bool initialize(std::ifstream* file);
|
||||
|
||||
size_t read(char *data, size_t size);
|
||||
|
||||
bool seek(size_t position);
|
||||
|
||||
size_t length();
|
||||
|
||||
void cleanup();
|
||||
|
||||
protected:
|
||||
std::ifstream* m_file;
|
||||
size_t m_currentPosition;
|
||||
size_t m_dataStart;
|
||||
size_t m_dataSize;
|
||||
};
|
||||
|
||||
#endif // WAVFILE_H
|
81
src/z80intf.cpp
Normal file
81
src/z80intf.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
#include "z80intf.h"
|
||||
#include "3rdparty/ym/ym2610.h"
|
||||
#include "neogeocd.h"
|
||||
|
||||
extern "C" {
|
||||
uint16_t io_read_byte_8(uint16_t port)
|
||||
{
|
||||
switch (port & 0xFF)
|
||||
{
|
||||
case 0x00: // Sound code
|
||||
return neocd.audioCommand;
|
||||
|
||||
case 0x04: // Status port A
|
||||
return YM2610Read(0);
|
||||
|
||||
case 0x05: // Read port A
|
||||
return YM2610Read(1);
|
||||
|
||||
case 0x06: // Status port B
|
||||
return YM2610Read(2);
|
||||
|
||||
case 0x07: // Read port B
|
||||
return YM2610Read(3);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void io_write_byte_8(uint16_t port, uint16_t value)
|
||||
{
|
||||
switch (port & 0xFF)
|
||||
{
|
||||
case 0x00: // Clear sound code
|
||||
neocd.audioCommand = 0;
|
||||
break;
|
||||
|
||||
case 0x04: // Control port A
|
||||
YM2610Write(0, (uint8_t)value);
|
||||
break;
|
||||
|
||||
case 0x05: // Data port A
|
||||
YM2610Write(1, (uint8_t)value);
|
||||
break;
|
||||
|
||||
case 0x06: // Control port B
|
||||
YM2610Write(2, (uint8_t)value);
|
||||
break;
|
||||
|
||||
case 0x07: // Data port B
|
||||
YM2610Write(3, (uint8_t)value);
|
||||
break;
|
||||
|
||||
case 0x08: // NMI Enable
|
||||
neocd.z80NMIDisable = false;
|
||||
break;
|
||||
|
||||
case 0x0C: // Set audio result
|
||||
neocd.audioResult = value;
|
||||
break;
|
||||
|
||||
case 0x18: // NMI Disable
|
||||
neocd.z80NMIDisable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t program_read_byte_8(uint16_t addr)
|
||||
{
|
||||
return neocd.memory.z80Ram[addr];
|
||||
}
|
||||
|
||||
void program_write_byte_8(uint16_t addr, uint8_t value)
|
||||
{
|
||||
neocd.memory.z80Ram[addr] = value;
|
||||
}
|
||||
|
||||
int z80_irq_callback(int parameter)
|
||||
{
|
||||
return 0x38;
|
||||
}
|
||||
}
|
37
src/z80intf.h
Normal file
37
src/z80intf.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef Z80INTF_H
|
||||
#define Z80INTF_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define Uint8 uint8_t
|
||||
#define Sint8 int8_t
|
||||
#define Uint16 uint16_t
|
||||
#define Uint32 uint32_t
|
||||
|
||||
#ifndef INLINE
|
||||
#ifdef _MSC_VER
|
||||
#define INLINE static __inline
|
||||
#else
|
||||
#define INLINE static __inline__
|
||||
#endif /* _MSC_VER */
|
||||
#endif /* INLINE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
uint16_t io_read_byte_8(uint16_t port);
|
||||
|
||||
void io_write_byte_8(uint16_t port, uint16_t value);
|
||||
|
||||
uint8_t program_read_byte_8(uint16_t addr);
|
||||
|
||||
void program_write_byte_8(uint16_t addr, uint8_t value);
|
||||
|
||||
int z80_irq_callback(int parameter);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // Z80INTF_H
|
Loading…
Reference in New Issue
Block a user