Initial commit.

This commit is contained in:
Fabrice Martinez 2018-09-25 21:16:56 +02:00
commit 48f9d59d9f
81 changed files with 61279 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build
distclean
.clang-format

115
CMakeLists.txt Normal file
View 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
View 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
View 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 |
> **&#128211; 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.
> **&#128211; 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]).
> **&#127926; 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

View 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)

View 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)

View 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
View File

@ -0,0 +1,7 @@
vcodec = libx264
acodec = libmp3lame
pix_fmt = yuv444p
video_preset = veryslow
sample_rate = 44100
video_qp = 0
threads = 3

View 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
View 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
View 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
View 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" , &REG_PPC, 1);
state_save_register_UINT32(type, cpu, "PC" , &REG_PC, 1);
state_save_register_UINT32(type, cpu, "USP" , &REG_USP, 1);
state_save_register_UINT32(type, cpu, "ISP" , &REG_ISP, 1);
state_save_register_UINT32(type, cpu, "MSP" , &REG_MSP, 1);
state_save_register_UINT32(type, cpu, "VBR" , &REG_VBR, 1);
state_save_register_UINT32(type, cpu, "SFC" , &REG_SFC, 1);
state_save_register_UINT32(type, cpu, "DFC" , &REG_DFC, 1);
state_save_register_UINT32(type, cpu, "CACR" , &REG_CACR, 1);
state_save_register_UINT32(type, cpu, "CAAR" , &REG_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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

58
src/3rdparty/ym/ym2610.h vendored Normal file
View 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

File diff suppressed because it is too large Load Diff

74
src/3rdparty/z80/z80.h vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

164
src/m68kintf.cpp Normal file
View 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*>(&region->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*>(&region->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
View 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
View 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 = &regionLookupTable[memoryRegions[i].startAddress / MEMORY_GRANULARITY];
ptr <= &regionLookupTable[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*>(&region->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*>(&region->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 *>(&regValue), 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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