├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── kotlin │ └── com │ │ └── kotcrab │ │ └── xgbc │ │ ├── Assets.kt │ │ ├── DebuggerListener.kt │ │ ├── Emulator.kt │ │ ├── EmulatorException.kt │ │ ├── Util.kt │ │ ├── XGBC.kt │ │ ├── cpu │ │ ├── Cpu.kt │ │ ├── OpCodes.kt │ │ └── OpCodesProcessor.kt │ │ ├── gdx │ │ ├── GdxGpu.kt │ │ └── GdxJoypad.kt │ │ ├── io │ │ ├── Div.kt │ │ ├── Gpu.kt │ │ ├── IO.kt │ │ ├── Joypad.kt │ │ ├── Lcd.kt │ │ ├── SerialPort.kt │ │ └── Timer.kt │ │ ├── rom │ │ ├── Rom.kt │ │ ├── RomEnums.kt │ │ └── mbc │ │ │ ├── MBC.kt │ │ │ ├── MBC1.kt │ │ │ └── RomOnly.kt │ │ └── ui │ │ ├── CpuDebuggerTable.kt │ │ ├── DebuggerPopupMenu.kt │ │ ├── DebuggerWindow.kt │ │ ├── EmulatorWindow.kt │ │ ├── OpCodeLine.kt │ │ ├── OpCodesDebuggerTab.kt │ │ ├── StackPointerView.kt │ │ ├── TableBuilder.kt │ │ └── VRAMWindow.kt └── resources │ └── gfx │ ├── ui.atlas │ └── ui.png └── test ├── kotlin └── com │ └── kotcrab │ └── xgbc │ └── test │ ├── TestCbOpCodesTimings.kt │ ├── TestConditionalOpCodesTimings.kt │ ├── TestOpCodesLengths.kt │ ├── TestOpCodesTimings.kt │ └── TestUtils.kt └── resources └── test_rom.gb /.gitignore: -------------------------------------------------------------------------------- 1 | src/main/resources/rom/* 2 | 3 | ## Maven 4 | target/ 5 | 6 | ## Java 7 | 8 | *.class 9 | *.war 10 | *.ear 11 | hs_err_pid* 12 | 13 | ## GWT 14 | 15 | war/gwt_bree/ 16 | gwt-unitCache/ 17 | .apt_generated/ 18 | war/WEB-INF/deploy/ 19 | war/WEB-INF/classes/ 20 | .gwt/ 21 | gwt-unitCache/ 22 | www-test/ 23 | .gwt-tmp/ 24 | 25 | ## Android Studio and Intellij 26 | 27 | .idea/ 28 | *.ipr 29 | *.iws 30 | *.iml 31 | out/ 32 | com_crashlytics_export_strings.xml 33 | 34 | ## Eclipse 35 | 36 | .metadata 37 | bin/ 38 | tmp/ 39 | *.tmp 40 | *.bak 41 | *.swp 42 | *~.nib 43 | local.properties 44 | .settings/ 45 | .loadpath 46 | .pydevproject 47 | .project 48 | .classpath 49 | .externalToolBuilders/ 50 | *.launch 51 | 52 | ## NetBeans 53 | nbproject/private/ 54 | build/ 55 | nbbuild/ 56 | dist/ 57 | nbdist/ 58 | nbactions.xml 59 | nb-configuration.xml 60 | 61 | ## Gradle 62 | 63 | .gradle 64 | build/ 65 | /target 66 | /target 67 | /target 68 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of the AUTHORS of Vis Project 2 | # for copyright purposes. 3 | # This file is distinct from the CONTRIBUTORS files. 4 | # See the latter for an explanation. 5 | 6 | # Names should be added to this file as 7 | # Name or Organization 8 | # The email address is not required for organizations. 9 | 10 | Pawel Pastuszak 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xgbc 2 | 3 | Game Boy emulator written in Kotlin using libGDX and [VisUI](https://github.com/kotcrab/vis-editor/wiki/VisUI) 4 | 5 | #### Status 6 | * Working cpu emulation 7 | * `cpu_instrs`, `instr_timing` and `mem_timing` tests are passing. 8 | * Debugger with 'Run to Line' function and breakpoints 9 | * VRAM viewer and initial GPU emulation (tilemap rendering) 10 | * MBC1 controller, Serial port, Joypad, Timer, DIV emulation implemented 11 | 12 | Test roms: 13 | ``` 14 | cpu_instrs 15 | 16 | 01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok 17 | 18 | Passed all tests 19 | ``` 20 | 21 | ``` 22 | instr_timing 23 | 24 | 25 | Passed 26 | ``` 27 | 28 | ``` 29 | mem_timing 30 | 31 | 01:ok 02:ok 03:ok 32 | 33 | Passed 34 | ``` 35 | 36 | Running Tetris (without sprites support) 37 | ![Tetris Screenshot](http://dl.kotcrab.com/img/d/2017-02-25_2014.png) 38 | 39 | VRAM viewer (showing first tile palette) and debugger: 40 | ![VRAM Screenshot](http://dl.kotcrab.com/img/d/2016-04-20_2341.png) 41 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.kotcrab.xgbc' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.0.4' 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'kotlin' 17 | 18 | repositories { 19 | mavenCentral() 20 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 21 | maven { url "https://oss.sonatype.org/content/repositories/releases/" } 22 | } 23 | 24 | ext { 25 | gdxVersion = '1.9.3' 26 | visuiVersion = '1.1.1' 27 | } 28 | 29 | task wrapper(type: Wrapper) { 30 | gradleVersion = '3.1' 31 | } 32 | 33 | dependencies { 34 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 35 | compile "com.badlogicgames.gdx:gdx:$gdxVersion" 36 | compile "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion" 37 | compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" 38 | compile "com.kotcrab.vis:vis-ui:$visuiVersion" 39 | testCompile group: 'junit', name: 'junit', version: '4.11' 40 | } 41 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kotcrab/xgbc/25d2623acf0f1e87cb7a2db61c26af4d2d74f735/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Nov 05 20:04:18 CET 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'xgbc' 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/Assets.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.g2d.TextureAtlas 5 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable 6 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable 7 | 8 | /** @author Kotcrab */ 9 | object Assets { 10 | private lateinit var ui: TextureAtlas 11 | 12 | fun load() { 13 | ui = TextureAtlas(Gdx.files.internal("gfx/ui.atlas")) 14 | } 15 | 16 | fun dispose() { 17 | ui.dispose() 18 | } 19 | 20 | fun get(icon: Icon): Drawable { 21 | return TextureRegionDrawable(ui.findRegion(icon.iconName)) 22 | } 23 | } 24 | 25 | enum class Icon(val iconName: String) { 26 | BREAKPOINT("breakpoint"), CURRENT_LINE("current-line"), 27 | BREAKPOINT_CURRENT_LINE("breakpoint-current-line") 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/DebuggerListener.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc 2 | 3 | import com.kotcrab.xgbc.cpu.Reg 4 | 5 | /** @author Kotcrab */ 6 | interface DebuggerListener { 7 | fun onRegisterWrite(reg: Reg, value: Byte) { 8 | 9 | } 10 | 11 | fun onMemoryWrite(addr: Int, value: Byte) { 12 | 13 | } 14 | 15 | fun onCpuTick(oldPc: Int, pc: Int) { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/Emulator.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc 2 | 3 | import com.badlogic.gdx.files.FileHandle 4 | import com.kotcrab.xgbc.cpu.Cpu 5 | import com.kotcrab.xgbc.cpu.Reg 6 | import com.kotcrab.xgbc.cpu.Reg16 7 | import com.kotcrab.xgbc.io.Gpu 8 | import com.kotcrab.xgbc.io.IO 9 | import com.kotcrab.xgbc.rom.Rom 10 | import java.util.* 11 | import com.badlogic.gdx.utils.Array as GdxArray 12 | 13 | /** @author Kotcrab */ 14 | class Emulator(romFile: FileHandle) { 15 | companion object { 16 | val BASE_CLOCK = 4096 //khz 17 | val CLOCK = BASE_CLOCK * 1000 / 60 18 | val REG_IF = 0xFF0F 19 | val REG_IE = 0xFFFF 20 | } 21 | 22 | val rom = Rom(romFile) 23 | val cpu = Cpu(this) 24 | val gpu = Gpu(this) 25 | val io = IO(this) 26 | 27 | var debuggerListener: DebuggerListener = DebuggerDelegate() 28 | private set 29 | var debuggerDelegates = GdxArray() 30 | 31 | private val ram: ByteArray = ByteArray(0x2000) 32 | private val oam: ByteArray = ByteArray(0xA0) 33 | private val internalRam: ByteArray = ByteArray(0x7F) 34 | 35 | /** Interrupt Enable */ 36 | private var ie: Byte = 0 37 | 38 | val interruptHandlers = ArrayList<(Interrupt) -> Unit>() 39 | var lcdTransferHandler: () -> Unit = {} 40 | var mode: EmulatorMode = EmulatorMode.NORMAL 41 | 42 | init { 43 | reset() 44 | } 45 | 46 | public fun reset() { 47 | mode = EmulatorMode.NORMAL 48 | debuggerDelegates.clear() 49 | 50 | ram.fill(0) 51 | oam.fill(0) 52 | internalRam.fill(0) 53 | gpu.reset() 54 | io.reset() 55 | ie = 0 56 | 57 | cpu.sp = 0xFFFE 58 | cpu.pc = 0x0100 59 | cpu.writeReg(Reg16.AF, 0x1180) 60 | cpu.writeReg(Reg16.BC, 0x0000) 61 | cpu.writeReg(Reg16.DE, 0xFF56) 62 | cpu.writeReg(Reg16.HL, 0x000D) 63 | write(0xFF05, 0x00) //TIMA 64 | write(0xFF06, 0x00) //TMA 65 | write(0xFF07, 0x00) //TAC 66 | write(0xFF10, 0x80) //NR10 67 | write(0xFF11, 0xBF) //NR11 68 | write(0xFF12, 0xF3) //NR12 69 | write(0xFF14, 0xBF) //NR14 70 | write(0xFF16, 0x3F) //NR21 71 | write(0xFF17, 0x00) //NR22 72 | write(0xFF19, 0xBF) //NR24 73 | write(0xFF1A, 0x7F) //NR30 74 | write(0xFF1B, 0xFF) //NR31 75 | write(0xFF1C, 0x9F) //NR32 76 | write(0xFF1E, 0xBF) //NR33 77 | write(0xFF20, 0xFF) //NR41 78 | write(0xFF21, 0x00) //NR42 79 | write(0xFF22, 0x00) //NR43 80 | write(0xFF23, 0xBF) //NR30 81 | write(0xFF24, 0x77) //NR50 82 | write(0xFF25, 0xF3) //NR51 83 | write(0xFF26, 0xF1) //NR52 (F1-GB, F0-SGB) 84 | write(0xFF40, 0x91) //LCDC 85 | write(0xFF42, 0x00) //SCY 86 | write(0xFF43, 0x00) //SCX 87 | write(0xFF45, 0x00) //LYC 88 | write(0xFF47, 0xFC) //BGP 89 | write(0xFF48, 0xFF) //OBP0 90 | write(0xFF49, 0xFF) //OBP1 91 | write(0xFF4A, 0x00) //WY 92 | write(0xFF4B, 0x00) //WX 93 | write(0xFFFF, 0x00) //IE 94 | } 95 | 96 | fun update(updateBreaker: () -> Boolean = { false }) { 97 | val targetCycles = cpu.cycle + CLOCK 98 | var manuallySyncedCycles = 0 99 | 100 | while (true) { 101 | step() 102 | manuallySyncedCycles += io.getAndClearSyncedCycles() 103 | if (cpu.cycle + manuallySyncedCycles > targetCycles) { 104 | break 105 | } 106 | 107 | if (updateBreaker.invoke()) { 108 | break 109 | } 110 | } 111 | } 112 | 113 | fun step() { 114 | val cycles = cpu.cycle 115 | cpu.tick() 116 | io.tick((cpu.cycle - cycles).toInt()) 117 | } 118 | 119 | fun read(addr: Int): Byte { 120 | when (addr) { 121 | in 0x0000..0x8000 - 1 -> return rom.mbc.read(addr) 122 | in 0x8000..0xA000 - 1 -> return gpu.vram[addr - 0x8000] 123 | in 0xA000..0xC000 - 1 -> return rom.mbc.read(addr) 124 | in 0xC000..0xE000 - 1 -> return ram[addr - 0xC000] 125 | in 0xE000..0xFE00 - 1 -> return ram[addr - 0xE000] //ram echo 126 | in 0xFE00..0xFEA0 - 1 -> return oam[addr - 0xFE00] 127 | in 0xFEA0..0xFF00 - 1 -> return 0//throw EmulatorException("Read attempt from unusable, empty IO memory") 128 | in 0xFF00..0xFF4C - 1 -> return io.read(addr) 129 | in 0xFF4C..0xFF80 - 1 -> return 0//throw EmulatorException("Read attempt from unusable, empty IO memory") 130 | in 0xFF80..0xFFFF - 1 -> return internalRam[addr - 0xFF80] 131 | 0xFFFF -> return ie 132 | else -> throw EmulatorException("Read address out of range: " + toHex(addr)) 133 | } 134 | } 135 | 136 | fun readInt(addr: Int): Int { 137 | return read(addr).toUnsignedInt() 138 | } 139 | 140 | fun read16(addr: Int): Int { 141 | val ls = readInt(addr) 142 | val hs = readInt(addr + 1) 143 | 144 | return ((hs shl 8) or ls) 145 | } 146 | 147 | fun write(addr: Int, value: Byte) { 148 | when (addr) { 149 | in 0x0000..0x8000 - 1 -> rom.mbc.write(addr, value) 150 | in 0x8000..0xA000 - 1 -> gpu.vram[addr - 0x8000] = value 151 | in 0xA000..0xC000 - 1 -> rom.mbc.write(addr, value) 152 | in 0xC000..0xE000 - 1 -> ram[addr - 0xC000] = value 153 | in 0xE000..0xFE00 - 1 -> ram[addr - 0xE000] = value //ram echo 154 | in 0xFE00..0xFEA0 - 1 -> oam[addr - 0xFE00] = value 155 | in 0xFEA0..0xFF00 - 1 -> return//throw EmulatorException("Write attempt to unusable, empty IO memory") 156 | in 0xFF00..0xFF4C - 1 -> io.write(addr, value) 157 | in 0xFF4C..0xFF80 - 1 -> return//throw EmulatorException("Write attempt to unusable, empty IO memory") 158 | in 0xFF80..0xFFFF - 1 -> internalRam[addr - 0xFF80] = value 159 | 0xFFFF -> ie = value 160 | else -> throw EmulatorException("Write address out of range: " + toHex(addr)) 161 | } 162 | 163 | debuggerListener.onMemoryWrite(addr, value) 164 | } 165 | 166 | fun write(addr: Int, value: Int) { 167 | write(addr, value.toByte()) 168 | } 169 | 170 | fun write16(addr: Int, value: Int) { 171 | write(addr, value) 172 | write(addr + 1, value ushr 8) 173 | } 174 | 175 | fun addDebuggerListener(listener: DebuggerListener) { 176 | debuggerDelegates.add(listener) 177 | } 178 | 179 | inner class DebuggerDelegate : DebuggerListener { 180 | override fun onCpuTick(oldPc: Int, pc: Int) { 181 | if (debuggerDelegates.size == 0) return 182 | for (listener in debuggerDelegates) 183 | listener.onCpuTick(oldPc, pc) 184 | } 185 | 186 | override fun onMemoryWrite(addr: Int, value: Byte) { 187 | if (debuggerDelegates.size == 0) return 188 | for (listener in debuggerDelegates) 189 | listener.onMemoryWrite(addr, value) 190 | } 191 | 192 | override fun onRegisterWrite(reg: Reg, value: Byte) { 193 | if (debuggerDelegates.size == 0) return 194 | for (listener in debuggerDelegates) 195 | listener.onRegisterWrite(reg, value) 196 | } 197 | 198 | } 199 | 200 | fun interrupt(interrupt: Interrupt) { 201 | write(REG_IF, read(REG_IF).setBit(interrupt.interruptBit)) 202 | interruptHandlers.forEach { it.invoke(interrupt) } 203 | } 204 | } 205 | 206 | enum class Interrupt(val interruptBit: Int, val addr: Int) { 207 | //ordered by priority 208 | VBLANK(0, 0x0040), 209 | LCDC(1, 0x0048), 210 | TIMER(2, 0x0050), 211 | SERIAL(3, 0x0058), 212 | JOYPAD(4, 0x0060), 213 | } 214 | 215 | enum class EmulatorMode { 216 | NORMAL, DEBUGGING 217 | } 218 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/EmulatorException.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc 2 | 3 | class EmulatorException : RuntimeException { 4 | constructor(message: String) : super(message) { 5 | } 6 | 7 | constructor(cause: Throwable) : super(cause) { 8 | } 9 | 10 | constructor(message: String, cause: Throwable) : super(message, cause) { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/Util.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener 5 | 6 | fun Actor.changed(callback: (ChangeListener.ChangeEvent, Actor) -> Any?): ChangeListener { 7 | val listener = object : ChangeListener() { 8 | override fun changed(event: ChangeEvent?, actor: Actor?) { 9 | callback.invoke(event!!, actor!!) 10 | } 11 | } 12 | this.addListener(listener) 13 | return listener 14 | } 15 | 16 | fun toHex(value: Int) = String.format("%04X", value) 17 | fun toHex(value: Byte) = String.format("%02X", value) 18 | 19 | fun toBits(value: Byte) = String.format("%8s", Integer.toBinaryString(value.toInt() and 0xFF)).replace(" ", "0") 20 | 21 | fun Byte.toUnsignedInt(): Int = (this.toInt() and 0xFF) 22 | 23 | fun Byte.isBitSet(bit: Int): Boolean { 24 | if (bit >= 8) throw IllegalArgumentException("Out of range, bit must be <8") 25 | return (this.toUnsignedInt()) and (1 shl bit) != 0 26 | } 27 | 28 | fun Byte.setBit(bit: Int): Byte { 29 | if (bit >= 8) throw IllegalArgumentException("Out of range, bit must be <8") 30 | return (this.toUnsignedInt() or (1 shl bit)).toByte() 31 | } 32 | 33 | fun Byte.resetBit(bit: Int): Byte { 34 | if (bit >= 8) throw IllegalArgumentException("Out of range, bit must be <8") 35 | return (this.toUnsignedInt() and (1 shl bit).inv()).toByte() 36 | } 37 | 38 | fun Byte.toggleBit(bit: Int): Byte { 39 | if (bit >= 8) throw IllegalArgumentException("Out of range, bit must be <8") 40 | return (this.toUnsignedInt() xor (1 shl bit)).toByte() 41 | } 42 | 43 | fun Byte.setBitState(bit: Int, bitState: Boolean): Byte { 44 | if ((bitState && isBitSet(bit) == false) || (bitState == false && isBitSet(bit))) { 45 | return toggleBit(bit) 46 | } 47 | 48 | return this 49 | } 50 | 51 | fun Byte.rotateRight(dist: Int): Byte = ((this.toUnsignedInt() ushr dist) or ((this.toUnsignedInt()) shl (8 - dist))).toByte() 52 | fun Byte.rotateLeft(dist: Int): Byte = ((this.toUnsignedInt() shl dist) or ((this.toUnsignedInt()) ushr (8 - dist))).toByte() 53 | 54 | fun Boolean.toInt(): Int = if (this) 1 else 0 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/XGBC.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc 2 | 3 | import com.badlogic.gdx.ApplicationAdapter 4 | import com.badlogic.gdx.Gdx 5 | import com.badlogic.gdx.Input 6 | import com.badlogic.gdx.InputMultiplexer 7 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application 8 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration 9 | import com.badlogic.gdx.graphics.GL20 10 | import com.badlogic.gdx.scenes.scene2d.InputEvent 11 | import com.badlogic.gdx.scenes.scene2d.InputListener 12 | import com.badlogic.gdx.scenes.scene2d.Stage 13 | import com.badlogic.gdx.utils.viewport.ScreenViewport 14 | import com.kotcrab.vis.ui.VisUI 15 | import com.kotcrab.vis.ui.widget.VisTable 16 | import com.kotcrab.xgbc.gdx.GdxJoypad 17 | import com.kotcrab.xgbc.ui.DebuggerWindow 18 | import com.kotcrab.xgbc.ui.EmulatorWindow 19 | import com.kotcrab.xgbc.ui.VRAMWindow 20 | 21 | /** @author Kotcrab */ 22 | class XGBC : ApplicationAdapter() { 23 | private lateinit var stage: Stage 24 | private lateinit var emulator: Emulator 25 | 26 | override fun create() { 27 | VisUI.load() 28 | Assets.load() 29 | stage = Stage(ScreenViewport()) 30 | val root = VisTable() 31 | root.setFillParent(true) 32 | 33 | stage.addActor(root) 34 | 35 | emulator = Emulator(Gdx.files.internal("rom/test/cpu_instrs.gb")) 36 | //emulator = Emulator(Gdx.files.internal("rom/test/instr_timing.gb")) 37 | //emulator = Emulator(Gdx.files.internal("rom/test/mem_timing_v2.gb")) 38 | //emulator = Emulator(Gdx.files.internal("rom/test/mem/01-read_timing.gb")) 39 | //emulator = Emulator(Gdx.files.internal("rom/test/mem/02-write_timing.gb")) 40 | //emulator = Emulator(Gdx.files.internal("rom/test/mem/03-modify_timing.gb")) 41 | //emulator = Emulator(Gdx.files.internal("rom/test/oam_bug_v2.gb")) 42 | //emulator = Emulator(Gdx.files.internal("rom/tetrisdx.gbc")) 43 | //emulator = Emulator(Gdx.files.internal("rom/pokemon_gold.gbc")) 44 | //emulator = Emulator(Gdx.files.internal("rom/BADAPPLE.gbc")) 45 | //emulator = Emulator(Gdx.files.internal("rom/bubble.gbc")) 46 | //emulator = Emulator(Gdx.files.internal("rom/tetris.gb")) 47 | //emulator = Emulator(Gdx.files.internal("rom/GBTICTAC.GB")) 48 | 49 | stage.addActor(EmulatorWindow(emulator)) 50 | stage.addActor(VRAMWindow(emulator)) 51 | val debug = true 52 | var debuggerWindow: DebuggerWindow? = null 53 | if (debug) { 54 | debuggerWindow = DebuggerWindow(emulator) 55 | stage.addActor(debuggerWindow) 56 | } 57 | 58 | stage.addListener(object : InputListener() { 59 | override fun keyDown(event: InputEvent?, keycode: Int): Boolean { 60 | if (keycode == Input.Keys.F1) { 61 | emulator.reset() 62 | if(debuggerWindow != null) { 63 | (debuggerWindow as DebuggerWindow).remove() 64 | debuggerWindow = DebuggerWindow(emulator) 65 | stage.addActor(debuggerWindow) 66 | } 67 | } 68 | return super.keyDown(event, keycode) 69 | } 70 | }) 71 | 72 | val inputMultiplexer = InputMultiplexer() 73 | inputMultiplexer.addProcessor(GdxJoypad(emulator.io.joypad)) 74 | inputMultiplexer.addProcessor(stage) 75 | Gdx.input.inputProcessor = inputMultiplexer 76 | } 77 | 78 | override fun resize(width: Int, height: Int) { 79 | if (width == 0 && height == 0) return 80 | stage.viewport.update(width, height, true) 81 | } 82 | 83 | override fun render() { 84 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) 85 | stage.act(Math.min(Gdx.graphics.deltaTime, 1 / 30f)) 86 | stage.draw() 87 | 88 | if (emulator.mode == EmulatorMode.NORMAL) { 89 | emulator.update() 90 | } 91 | } 92 | 93 | override fun dispose() { 94 | stage.dispose() 95 | Assets.dispose() 96 | VisUI.dispose() 97 | } 98 | } 99 | 100 | fun main(args: Array) { 101 | val config = Lwjgl3ApplicationConfiguration() 102 | config.setWindowedMode(1280, 720) 103 | config.setTitle("XGBC") 104 | Lwjgl3Application(XGBC(), config) 105 | } 106 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/cpu/Cpu.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.cpu 2 | 3 | import com.kotcrab.xgbc.* 4 | 5 | /** @author Kotcrab */ 6 | class Cpu(private val emulator: Emulator) { 7 | var sp: Int = 0 //stack pointer 8 | var pc: Int = 0 //program counter 9 | var cycle: Long = 0 10 | private set 11 | var halt: Boolean = false 12 | private val regs: ByteArray = ByteArray(8) 13 | var ime = false //interrupt master enable 14 | private set 15 | private var targetIme = false 16 | private var changeImeState = ImeState.IDLE 17 | 18 | val op = arrayOfNulls(256) 19 | val extOp = arrayOfNulls(256) 20 | 21 | val opProc = OpCodesProcessor(emulator, this) 22 | 23 | init { 24 | generateOpCodes(emulator, this, opProc, op) 25 | generateExtOpCodes(opProc, extOp) 26 | } 27 | 28 | fun readReg(reg: Reg): Byte { 29 | return regs[reg.index] 30 | } 31 | 32 | fun readReg(reg16: Reg16): Int { 33 | val r1 = readRegInt(reg16.highReg) 34 | val r2 = readRegInt(reg16.lowReg) 35 | return ((r1 shl 8) or r2) 36 | } 37 | 38 | fun readRegInt(reg: Reg): Int { 39 | return readReg(reg).toUnsignedInt() 40 | } 41 | 42 | fun writeReg(reg: Reg, value: Byte) { 43 | var setValue = value 44 | if (reg == Reg.F) { 45 | //reset four lsb bits 46 | setValue = (value.toUnsignedInt() and 0xF0).toByte() 47 | } 48 | regs[reg.index] = setValue 49 | emulator.debuggerListener.onRegisterWrite(reg, setValue) 50 | } 51 | 52 | fun writeReg(reg16: Reg16, value: Int) { 53 | writeReg(reg16.highReg, (value ushr 8).toByte()) 54 | writeReg(reg16.lowReg, (value).toByte()) 55 | } 56 | 57 | fun setImeFlag(ime: Boolean) { 58 | if (this.ime == ime) return 59 | targetIme = ime 60 | changeImeState = ImeState.CHANGE_AFTER_NEXT 61 | } 62 | 63 | fun setImeFlagNow(ime: Boolean) { 64 | this.ime = ime 65 | } 66 | 67 | fun setFlag(flag: Flag) { 68 | var flagReg = readRegInt(Reg.F) 69 | flagReg = flagReg or (1 shl flag.bit) 70 | writeReg(Reg.F, flagReg.toByte()) 71 | } 72 | 73 | fun resetFlag(flag: Flag) { 74 | var flagReg = readRegInt(Reg.F) 75 | flagReg = flagReg and (1 shl flag.bit).inv() 76 | writeReg(Reg.F, flagReg.toByte()) 77 | } 78 | 79 | fun toggleFlag(flag: Flag) { 80 | var flagReg = readRegInt(Reg.F) 81 | flagReg = flagReg xor (1 shl flag.bit) 82 | writeReg(Reg.F, flagReg.toByte()) 83 | } 84 | 85 | fun setFlagState(flag: Flag, flagState: Boolean) { 86 | if ((flagState && isFlagSet(flag) == false) || (flagState == false && isFlagSet(flag))) { 87 | toggleFlag(flag) 88 | } 89 | } 90 | 91 | fun isFlagSet(flag: Flag): Boolean { 92 | val flagReg = readRegInt(Reg.F) 93 | return flagReg and (1 shl flag.bit) != 0 94 | } 95 | 96 | fun tick() { 97 | processInterrupts() 98 | 99 | if (halt) { 100 | cycle += 1 101 | emulator.debuggerListener.onCpuTick(pc, pc) 102 | return 103 | } 104 | 105 | val oldPc = pc 106 | val opcode = emulator.readInt(pc) 107 | 108 | val instr: Instr? 109 | if (opcode == 0xCB) { 110 | instr = emulator.cpu.extOp[emulator.readInt(pc + 1)] 111 | } else { 112 | instr = emulator.cpu.op[opcode] 113 | } 114 | 115 | if (instr == null) throw EmulatorException("Illegal opcode: ${toHex(opcode.toByte())} at ${toHex(pc)}") 116 | 117 | if (instr is JmpInstr) { 118 | val result = instr.jmpOp.invoke() 119 | if (result == false) { 120 | pc += instr.len 121 | cycle += instr.cycles 122 | } else { 123 | cycle += instr.cyclesIfTaken 124 | } 125 | } else { 126 | instr.op.invoke() 127 | if (oldPc != pc) { 128 | println("Warn: PC modification by non JmpInstr will be ignored, opcode: ${toHex(opcode.toByte())}") 129 | } 130 | pc += instr.len 131 | cycle += instr.cycles 132 | } 133 | 134 | when (changeImeState) { 135 | ImeState.IDLE -> { 136 | } 137 | ImeState.CHANGE_AFTER_NEXT -> { 138 | changeImeState = ImeState.CHANGE_IME 139 | } 140 | ImeState.CHANGE_IME -> { 141 | ime = targetIme 142 | changeImeState = ImeState.IDLE 143 | } 144 | } 145 | 146 | emulator.debuggerListener.onCpuTick(oldPc, pc) 147 | } 148 | 149 | private fun processInterrupts() { 150 | val ie = emulator.read(Emulator.REG_IE) 151 | val if_ = emulator.read(Emulator.REG_IF) 152 | 153 | for (interrupt in Interrupt.values()) { 154 | if (ie.isBitSet(interrupt.interruptBit) && if_.isBitSet(interrupt.interruptBit)) { 155 | halt = false 156 | if (ime == false) return 157 | emulator.write(Emulator.REG_IF, emulator.read(Emulator.REG_IF).resetBit(interrupt.interruptBit)) 158 | ime = false 159 | opProc.push(pc) 160 | cycle += 16 161 | pc = interrupt.addr 162 | break 163 | } 164 | } 165 | } 166 | 167 | private enum class ImeState { 168 | IDLE, 169 | CHANGE_AFTER_NEXT, 170 | CHANGE_IME, 171 | } 172 | } 173 | 174 | enum class Reg(val index: Int) { 175 | A(0), F(1), 176 | B(2), C(3), 177 | D(4), E(5), 178 | H(6), L(7) 179 | } 180 | 181 | enum class Reg16(val highReg: Reg, val lowReg: Reg) { 182 | AF(Reg.A, Reg.F), 183 | BC(Reg.B, Reg.C), 184 | DE(Reg.D, Reg.E), 185 | HL(Reg.H, Reg.L) 186 | } 187 | 188 | enum class Flag(val bit: Int) { 189 | Z(7), N(6), H(5), C(4) 190 | } 191 | 192 | open class Instr(val len: Int, 193 | val cycles: Int, 194 | val mnemonic: String, 195 | val op: () -> Unit, 196 | val internalCycles: Int = 0, 197 | val realCycles: Int = internalCycles + cycles) 198 | 199 | class JmpInstr(len: Int, 200 | val cyclesIfTaken: Int, 201 | cycles: Int, 202 | mnemonic: String, 203 | val jmpOp: () -> Boolean) : Instr(len, cycles, mnemonic, { jmpOp() }) 204 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/cpu/OpCodes.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.cpu 2 | 3 | import com.kotcrab.xgbc.Emulator 4 | import com.kotcrab.xgbc.EmulatorException 5 | 6 | /** @author Kotcrab */ 7 | 8 | fun generateOpCodes(emu: Emulator, cpu: Cpu, proc: OpCodesProcessor, op: Array) { 9 | op[0x00] = Instr(1, 4, "NOP", {}) 10 | op[0x01] = Instr(3, 12, "LD BC, d16", { proc.ld16ImmValueToReg(Reg16.BC) }) 11 | op[0x02] = Instr(1, 8, "LD (BC), A", { proc.ld8RegToReg16Addr(Reg16.BC, Reg.A) }) 12 | op[0x03] = Instr(1, 8, "INC BC", { proc.inc16(Reg16.BC) }) 13 | op[0x04] = Instr(1, 4, "INC B", { proc.inc(Reg.B) }) 14 | op[0x05] = Instr(1, 4, "DEC B", { proc.dec(Reg.B) }) 15 | op[0x06] = Instr(2, 8, "LD B, d8", { proc.ld8ImmValueToReg(Reg.B) }) 16 | op[0x07] = Instr(1, 4, "RLCA", { proc.rlcReg(Reg.A, false) }) 17 | op[0x08] = Instr(3, 20, "LD (a16), SP", { emu.write16(emu.read16(cpu.pc + 1), cpu.sp) }) 18 | op[0x09] = Instr(1, 8, "ADD HL, BC", { proc.addHL(Reg16.BC) }) 19 | op[0x0A] = Instr(1, 8, "LD A, (BC)", { proc.ld8Reg16AddrToReg(Reg.A, Reg16.BC) }) 20 | op[0x0B] = Instr(1, 8, "DEC BC", { proc.dec16(Reg16.BC) }) 21 | op[0x0C] = Instr(1, 4, "INC C", { proc.inc(Reg.C) }) 22 | op[0x0D] = Instr(1, 4, "DEC C", { proc.dec(Reg.C) }) 23 | op[0x0E] = Instr(2, 8, "LD C, d8", { proc.ld8ImmValueToReg(Reg.C) }) 24 | op[0x0F] = Instr(1, 4, "RRCA", { proc.rrcReg(Reg.A, false) }) 25 | op[0x10] = Instr(2, 4, "STOP 0", {}) 26 | op[0x11] = Instr(3, 12, "LD DE, d16", { proc.ld16ImmValueToReg(Reg16.DE) }) 27 | op[0x12] = Instr(1, 8, "LD (DE), A", { proc.ld8RegToReg16Addr(Reg16.DE, Reg.A) }) 28 | op[0x13] = Instr(1, 8, "INC DE", { proc.inc16(Reg16.DE) }) 29 | op[0x14] = Instr(1, 4, "INC D", { proc.inc(Reg.D) }) 30 | op[0x15] = Instr(1, 4, "DEC D", { proc.dec(Reg.D) }) 31 | op[0x16] = Instr(2, 8, "LD D, d8", { proc.ld8ImmValueToReg(Reg.D) }) 32 | op[0x17] = Instr(1, 4, "RLA", { proc.rlReg(Reg.A, false) }) 33 | op[0x18] = JmpInstr(2, 12, 12, "JR r8", { proc.jr() }) 34 | op[0x19] = Instr(1, 8, "ADD HL, DE", { proc.addHL(Reg16.DE) }) 35 | op[0x1A] = Instr(1, 8, "LD A, (DE)", { proc.ld8Reg16AddrToReg(Reg.A, Reg16.DE) }) 36 | op[0x1B] = Instr(1, 8, "DEC DE", { proc.dec16(Reg16.DE) }) 37 | op[0x1C] = Instr(1, 4, "INC E", { proc.inc(Reg.E) }) 38 | op[0x1D] = Instr(1, 4, "DEC E", { proc.dec(Reg.E) }) 39 | op[0x1E] = Instr(2, 8, "LD E, d8", { proc.ld8ImmValueToReg(Reg.E) }) 40 | op[0x1F] = Instr(1, 4, "RRA", { proc.rrReg(Reg.A, false) }) 41 | op[0x20] = JmpInstr(2, 12, 8, "JR NZ, r8", { proc.jrNZ() }) 42 | op[0x21] = Instr(3, 12, "LD HL, d16", { proc.ld16ImmValueToReg(Reg16.HL) }) 43 | op[0x22] = Instr(1, 8, "LD (HL+), A", { 44 | proc.ld8RegToReg16Addr(Reg16.HL, Reg.A) 45 | proc.inc16(Reg16.HL) 46 | }) 47 | op[0x23] = Instr(1, 8, "INC HL", { proc.inc16(Reg16.HL) }) 48 | op[0x24] = Instr(1, 4, "INC H", { proc.inc(Reg.H) }) 49 | op[0x25] = Instr(1, 4, "DEC H", { proc.dec(Reg.H) }) 50 | op[0x26] = Instr(2, 8, "LD H, d8", { proc.ld8ImmValueToReg(Reg.H) }) 51 | op[0x27] = Instr(1, 4, "DAA", { proc.daa() }) 52 | op[0x28] = JmpInstr(2, 12, 8, "JR Z, r8", { proc.jrZ() }) 53 | op[0x29] = Instr(1, 8, "ADD HL, HL", { proc.addHL(Reg16.HL) }) 54 | op[0x2A] = Instr(1, 8, "LD A, (HL+)", { 55 | proc.ld8Reg16AddrToReg(Reg.A, Reg16.HL) 56 | proc.inc16(Reg16.HL) 57 | }) 58 | op[0x2B] = Instr(1, 8, "DEC HL", { proc.dec16(Reg16.HL) }) 59 | op[0x2C] = Instr(1, 4, "INC L", { proc.inc(Reg.L) }) 60 | op[0x2D] = Instr(1, 4, "DEC L", { proc.dec(Reg.L) }) 61 | op[0x2E] = Instr(2, 8, "LD L, d8", { proc.ld8ImmValueToReg(Reg.L) }) 62 | op[0x2F] = Instr(1, 4, "CPL", { proc.cpl() }) 63 | op[0x30] = JmpInstr(2, 12, 8, "JR NC, r8", { proc.jrNC() }) 64 | op[0x31] = Instr(3, 12, "LD SP, d16", { cpu.sp = emu.read16(cpu.pc + 1) }) 65 | op[0x32] = Instr(1, 8, "LD (HL-), A", { 66 | proc.ld8RegToReg16Addr(Reg16.HL, Reg.A) 67 | proc.dec16(Reg16.HL) 68 | }) 69 | op[0x33] = Instr(1, 8, "INC SP", { cpu.sp += 1 }) 70 | op[0x34] = Instr(1, 8, "INC (HL)", { proc.incHL() }, internalCycles = 4) 71 | op[0x35] = Instr(1, 8, "DEC (HL)", { proc.decHL() }, internalCycles = 4) 72 | op[0x36] = Instr(2, 8, "LD (HL), d8", { 73 | val addr = emu.read(cpu.pc + 1) 74 | proc.syncTimer(4) 75 | emu.write(cpu.readReg(Reg16.HL), addr) 76 | }, internalCycles = 4) 77 | op[0x37] = Instr(1, 4, "SCF", { proc.scf() }) 78 | op[0x38] = JmpInstr(2, 12, 8, "JR C, r8", { proc.jrC() }) 79 | op[0x39] = Instr(1, 8, "ADD HL, SP", { proc.addHLSP() }) 80 | op[0x3A] = Instr(1, 8, "LD A, (HL-)", { 81 | proc.ld8Reg16AddrToReg(Reg.A, Reg16.HL) 82 | proc.dec16(Reg16.HL) 83 | }) 84 | op[0x3B] = Instr(1, 8, "DEC SP", { cpu.sp -= 1 }) 85 | op[0x3C] = Instr(1, 4, "INC A", { proc.inc(Reg.A) }) 86 | op[0x3D] = Instr(1, 4, "DEC A", { proc.dec(Reg.A) }) 87 | op[0x3E] = Instr(2, 8, "LD A, d8", { proc.ld8ImmValueToReg(Reg.A) }) 88 | op[0x3F] = Instr(1, 4, "CCF", { proc.ccf() }) 89 | op[0x40] = Instr(1, 4, "LD B, B", { proc.ld(Reg.B, Reg.B) }) 90 | op[0x41] = Instr(1, 4, "LD B, C", { proc.ld(Reg.B, Reg.C) }) 91 | op[0x42] = Instr(1, 4, "LD B, D", { proc.ld(Reg.B, Reg.D) }) 92 | op[0x43] = Instr(1, 4, "LD B, E", { proc.ld(Reg.B, Reg.E) }) 93 | op[0x44] = Instr(1, 4, "LD B, H", { proc.ld(Reg.B, Reg.H) }) 94 | op[0x45] = Instr(1, 4, "LD B, L", { proc.ld(Reg.B, Reg.L) }) 95 | op[0x46] = Instr(1, 8, "LD B, (HL)", { proc.ld8Reg16AddrToReg(Reg.B, Reg16.HL) }) 96 | op[0x47] = Instr(1, 4, "LD B, A", { proc.ld(Reg.B, Reg.A) }) 97 | op[0x48] = Instr(1, 4, "LD C, B", { proc.ld(Reg.C, Reg.B) }) 98 | op[0x49] = Instr(1, 4, "LD C, C", { proc.ld(Reg.C, Reg.C) }) 99 | op[0x4A] = Instr(1, 4, "LD C, D", { proc.ld(Reg.C, Reg.D) }) 100 | op[0x4B] = Instr(1, 4, "LD C, E", { proc.ld(Reg.C, Reg.E) }) 101 | op[0x4C] = Instr(1, 4, "LD C, H", { proc.ld(Reg.C, Reg.H) }) 102 | op[0x4D] = Instr(1, 4, "LD C, L", { proc.ld(Reg.C, Reg.L) }) 103 | op[0x4E] = Instr(1, 8, "LD C, (HL)", { proc.ld8Reg16AddrToReg(Reg.C, Reg16.HL) }) 104 | op[0x4F] = Instr(1, 4, "LD C, A", { proc.ld(Reg.C, Reg.A) }) 105 | op[0x50] = Instr(1, 4, "LD D, B", { proc.ld(Reg.D, Reg.B) }) 106 | op[0x51] = Instr(1, 4, "LD D, C", { proc.ld(Reg.D, Reg.C) }) 107 | op[0x52] = Instr(1, 4, "LD D, D", { proc.ld(Reg.D, Reg.D) }) 108 | op[0x53] = Instr(1, 4, "LD D, E", { proc.ld(Reg.D, Reg.E) }) 109 | op[0x54] = Instr(1, 4, "LD D, H", { proc.ld(Reg.D, Reg.H) }) 110 | op[0x55] = Instr(1, 4, "LD D, L", { proc.ld(Reg.D, Reg.L) }) 111 | op[0x56] = Instr(1, 8, "LD D, (HL)", { proc.ld8Reg16AddrToReg(Reg.D, Reg16.HL) }) 112 | op[0x57] = Instr(1, 4, "LD D, A", { proc.ld(Reg.D, Reg.A) }) 113 | op[0x58] = Instr(1, 4, "LD E, B", { proc.ld(Reg.E, Reg.B) }) 114 | op[0x59] = Instr(1, 4, "LD E, C", { proc.ld(Reg.E, Reg.C) }) 115 | op[0x5A] = Instr(1, 4, "LD E, D", { proc.ld(Reg.E, Reg.D) }) 116 | op[0x5B] = Instr(1, 4, "LD E, E", { proc.ld(Reg.E, Reg.E) }) 117 | op[0x5C] = Instr(1, 4, "LD E, H", { proc.ld(Reg.E, Reg.H) }) 118 | op[0x5D] = Instr(1, 4, "LD E, L", { proc.ld(Reg.E, Reg.L) }) 119 | op[0x5E] = Instr(1, 8, "LD E, (HL)", { proc.ld8Reg16AddrToReg(Reg.E, Reg16.HL) }) 120 | op[0x5F] = Instr(1, 4, "LD E, A", { proc.ld(Reg.E, Reg.A) }) 121 | op[0x60] = Instr(1, 4, "LD H, B", { proc.ld(Reg.H, Reg.B) }) 122 | op[0x61] = Instr(1, 4, "LD H, C", { proc.ld(Reg.H, Reg.C) }) 123 | op[0x62] = Instr(1, 4, "LD H, D", { proc.ld(Reg.H, Reg.D) }) 124 | op[0x63] = Instr(1, 4, "LD H, E", { proc.ld(Reg.H, Reg.E) }) 125 | op[0x64] = Instr(1, 4, "LD H, H", { proc.ld(Reg.H, Reg.H) }) 126 | op[0x65] = Instr(1, 4, "LD H, L", { proc.ld(Reg.H, Reg.L) }) 127 | op[0x66] = Instr(1, 8, "LD H, (HL)", { proc.ld8Reg16AddrToReg(Reg.H, Reg16.HL) }) 128 | op[0x67] = Instr(1, 4, "LD H, A", { proc.ld(Reg.H, Reg.A) }) 129 | op[0x68] = Instr(1, 4, "LD L, B", { proc.ld(Reg.L, Reg.B) }) 130 | op[0x69] = Instr(1, 4, "LD L, C", { proc.ld(Reg.L, Reg.C) }) 131 | op[0x6A] = Instr(1, 4, "LD L, D", { proc.ld(Reg.L, Reg.D) }) 132 | op[0x6B] = Instr(1, 4, "LD L, E", { proc.ld(Reg.L, Reg.E) }) 133 | op[0x6C] = Instr(1, 4, "LD L, H", { proc.ld(Reg.L, Reg.H) }) 134 | op[0x6D] = Instr(1, 4, "LD L, L", { proc.ld(Reg.L, Reg.L) }) 135 | op[0x6E] = Instr(1, 8, "LD L, (HL)", { proc.ld8Reg16AddrToReg(Reg.L, Reg16.HL) }) 136 | op[0x6F] = Instr(1, 4, "LD L, A", { proc.ld(Reg.L, Reg.A) }) 137 | op[0x70] = Instr(1, 8, "LD (HL), B", { proc.ld8RegToReg16Addr(Reg16.HL, Reg.B) }) 138 | op[0x71] = Instr(1, 8, "LD (HL), C", { proc.ld8RegToReg16Addr(Reg16.HL, Reg.C) }) 139 | op[0x72] = Instr(1, 8, "LD (HL), D", { proc.ld8RegToReg16Addr(Reg16.HL, Reg.D) }) 140 | op[0x73] = Instr(1, 8, "LD (HL), E", { proc.ld8RegToReg16Addr(Reg16.HL, Reg.E) }) 141 | op[0x74] = Instr(1, 8, "LD (HL), H", { proc.ld8RegToReg16Addr(Reg16.HL, Reg.H) }) 142 | op[0x75] = Instr(1, 8, "LD (HL), L", { proc.ld8RegToReg16Addr(Reg16.HL, Reg.L) }) 143 | op[0x76] = Instr(1, 4, "HALT", { cpu.halt = true }) 144 | op[0x77] = Instr(1, 8, "LD (HL), A", { proc.ld8RegToReg16Addr(Reg16.HL, Reg.A) }) 145 | op[0x78] = Instr(1, 4, "LD A, B", { proc.ld(Reg.A, Reg.B) }) 146 | op[0x79] = Instr(1, 4, "LD A, C", { proc.ld(Reg.A, Reg.C) }) 147 | op[0x7A] = Instr(1, 4, "LD A, D", { proc.ld(Reg.A, Reg.D) }) 148 | op[0x7B] = Instr(1, 4, "LD A, E", { proc.ld(Reg.A, Reg.E) }) 149 | op[0x7C] = Instr(1, 4, "LD A, H", { proc.ld(Reg.A, Reg.H) }) 150 | op[0x7D] = Instr(1, 4, "LD A, L", { proc.ld(Reg.A, Reg.L) }) 151 | op[0x7E] = Instr(1, 8, "LD A, (HL)", { proc.ld8Reg16AddrToReg(Reg.A, Reg16.HL) }) 152 | op[0x7F] = Instr(1, 4, "LD A, A", { proc.ld(Reg.A, Reg.A) }) 153 | op[0x80] = Instr(1, 4, "ADD A, B", { proc.addReg(Reg.B) }) 154 | op[0x81] = Instr(1, 4, "ADD A, C", { proc.addReg(Reg.C) }) 155 | op[0x82] = Instr(1, 4, "ADD A, D", { proc.addReg(Reg.D) }) 156 | op[0x83] = Instr(1, 4, "ADD A, E", { proc.addReg(Reg.E) }) 157 | op[0x84] = Instr(1, 4, "ADD A, H", { proc.addReg(Reg.H) }) 158 | op[0x85] = Instr(1, 4, "ADD A, L", { proc.addReg(Reg.L) }) 159 | op[0x86] = Instr(1, 8, "ADD A, (HL)", { proc.add(emu.readInt(cpu.readReg(Reg16.HL))) }) 160 | op[0x87] = Instr(1, 4, "ADD A, A", { proc.addReg(Reg.A) }) 161 | op[0x88] = Instr(1, 4, "ADC A, B", { proc.adcReg(Reg.B) }) 162 | op[0x89] = Instr(1, 4, "ADC A, C", { proc.adcReg(Reg.C) }) 163 | op[0x8A] = Instr(1, 4, "ADC A, D", { proc.adcReg(Reg.D) }) 164 | op[0x8B] = Instr(1, 4, "ADC A, E", { proc.adcReg(Reg.E) }) 165 | op[0x8C] = Instr(1, 4, "ADC A, H", { proc.adcReg(Reg.H) }) 166 | op[0x8D] = Instr(1, 4, "ADC A, L", { proc.adcReg(Reg.L) }) 167 | op[0x8E] = Instr(1, 8, "ADC A, (HL)", { proc.adc(emu.readInt(cpu.readReg(Reg16.HL))) }) 168 | op[0x8F] = Instr(1, 4, "ADC A, A", { proc.adcReg(Reg.A) }) 169 | op[0x90] = Instr(1, 4, "SUB B", { proc.subReg(Reg.B) }) 170 | op[0x91] = Instr(1, 4, "SUB C", { proc.subReg(Reg.C) }) 171 | op[0x92] = Instr(1, 4, "SUB D", { proc.subReg(Reg.D) }) 172 | op[0x93] = Instr(1, 4, "SUB E", { proc.subReg(Reg.E) }) 173 | op[0x94] = Instr(1, 4, "SUB H", { proc.subReg(Reg.H) }) 174 | op[0x95] = Instr(1, 4, "SUB L", { proc.subReg(Reg.L) }) 175 | op[0x96] = Instr(1, 8, "SUB (HL)", { proc.sub(emu.readInt(cpu.readReg(Reg16.HL))) }) 176 | op[0x97] = Instr(1, 4, "SUB A", { proc.subReg(Reg.A) }) 177 | op[0x98] = Instr(1, 4, "SBC A, B", { proc.sbcReg(Reg.B) }) 178 | op[0x99] = Instr(1, 4, "SBC A, C", { proc.sbcReg(Reg.C) }) 179 | op[0x9A] = Instr(1, 4, "SBC A, D", { proc.sbcReg(Reg.D) }) 180 | op[0x9B] = Instr(1, 4, "SBC A, E", { proc.sbcReg(Reg.E) }) 181 | op[0x9C] = Instr(1, 4, "SBC A, H", { proc.sbcReg(Reg.H) }) 182 | op[0x9D] = Instr(1, 4, "SBC A, L", { proc.sbcReg(Reg.L) }) 183 | op[0x9E] = Instr(1, 8, "SBC A, (HL)", { proc.sbc(emu.readInt(cpu.readReg(Reg16.HL))) }) 184 | op[0x9F] = Instr(1, 4, "SBC A, A", { proc.sbcReg(Reg.A) }) 185 | op[0xA0] = Instr(1, 4, "AND B", { proc.andReg(Reg.B) }) 186 | op[0xA1] = Instr(1, 4, "AND C", { proc.andReg(Reg.C) }) 187 | op[0xA2] = Instr(1, 4, "AND D", { proc.andReg(Reg.D) }) 188 | op[0xA3] = Instr(1, 4, "AND E", { proc.andReg(Reg.E) }) 189 | op[0xA4] = Instr(1, 4, "AND H", { proc.andReg(Reg.H) }) 190 | op[0xA5] = Instr(1, 4, "AND L", { proc.andReg(Reg.L) }) 191 | op[0xA6] = Instr(1, 8, "AND (HL)", { proc.and(emu.readInt(cpu.readReg(Reg16.HL))) }) 192 | op[0xA7] = Instr(1, 4, "AND A", { proc.andReg(Reg.A) }) 193 | op[0xA8] = Instr(1, 4, "XOR B", { proc.xorReg(Reg.B) }) 194 | op[0xA9] = Instr(1, 4, "XOR C", { proc.xorReg(Reg.C) }) 195 | op[0xAA] = Instr(1, 4, "XOR D", { proc.xorReg(Reg.D) }) 196 | op[0xAB] = Instr(1, 4, "XOR E", { proc.xorReg(Reg.E) }) 197 | op[0xAC] = Instr(1, 4, "XOR H", { proc.xorReg(Reg.H) }) 198 | op[0xAD] = Instr(1, 4, "XOR L", { proc.xorReg(Reg.L) }) 199 | op[0xAE] = Instr(1, 8, "XOR (HL)", { proc.xor(emu.readInt(cpu.readReg(Reg16.HL))) }) 200 | op[0xAF] = Instr(1, 4, "XOR A", { proc.xorReg(Reg.A) }) 201 | op[0xB0] = Instr(1, 4, "OR B", { proc.orReg(Reg.B) }) 202 | op[0xB1] = Instr(1, 4, "OR C", { proc.orReg(Reg.C) }) 203 | op[0xB2] = Instr(1, 4, "OR D", { proc.orReg(Reg.D) }) 204 | op[0xB3] = Instr(1, 4, "OR E", { proc.orReg(Reg.E) }) 205 | op[0xB4] = Instr(1, 4, "OR H", { proc.orReg(Reg.H) }) 206 | op[0xB5] = Instr(1, 4, "OR L", { proc.orReg(Reg.L) }) 207 | op[0xB6] = Instr(1, 8, "OR (HL)", { proc.or(emu.readInt(cpu.readReg(Reg16.HL))) }) 208 | op[0xB7] = Instr(1, 4, "OR A", { proc.orReg(Reg.A) }) 209 | op[0xB8] = Instr(1, 4, "CP B", { proc.cpReg(Reg.B) }) 210 | op[0xB9] = Instr(1, 4, "CP C", { proc.cpReg(Reg.C) }) 211 | op[0xBA] = Instr(1, 4, "CP D", { proc.cpReg(Reg.D) }) 212 | op[0xBB] = Instr(1, 4, "CP E", { proc.cpReg(Reg.E) }) 213 | op[0xBC] = Instr(1, 4, "CP H", { proc.cpReg(Reg.H) }) 214 | op[0xBD] = Instr(1, 4, "CP L", { proc.cpReg(Reg.L) }) 215 | op[0xBE] = Instr(1, 8, "CP (HL)", { proc.cp(emu.readInt(cpu.readReg(Reg16.HL))) }) 216 | op[0xBF] = Instr(1, 4, "CP A", { proc.cpReg(Reg.A) }) 217 | op[0xC0] = JmpInstr(1, 20, 8, "RET NZ", { proc.retNZ() }) 218 | op[0xC1] = Instr(1, 12, "POP BC", { proc.popReg(Reg16.BC) }) 219 | op[0xC2] = JmpInstr(3, 16, 12, "JP NZ, a16", { proc.jpNZ() }) 220 | op[0xC3] = JmpInstr(3, 16, 16, "JP a16", { proc.jp() }) 221 | op[0xC4] = JmpInstr(3, 24, 12, "CALL NZ, a16", { proc.callNZ() }) 222 | op[0xC5] = Instr(1, 16, "PUSH BC", { proc.pushReg(Reg16.BC) }) 223 | op[0xC6] = Instr(2, 8, "ADD A, d8", { proc.add(emu.readInt(cpu.pc + 1)) }) 224 | op[0xC7] = JmpInstr(1, 16, 16, "RST 00H", { proc.rst(0x00) }) 225 | op[0xC8] = JmpInstr(1, 20, 8, "RET Z", { proc.retZ() }) 226 | op[0xC9] = JmpInstr(1, 16, 16, "RET", { proc.ret() }) 227 | op[0xCA] = JmpInstr(3, 16, 12, "JP Z, a16", { proc.jpZ() }) 228 | op[0xCB] = Instr(1, 4, "PREFIX CB", { throw EmulatorException("Reserved opcode, call instruction from CB opcode table") }) 229 | op[0xCC] = JmpInstr(3, 24, 12, "CALL Z, a16", { proc.callZ() }) 230 | op[0xCD] = JmpInstr(3, 24, 24, "CALL a16", { proc.call() }) 231 | op[0xCE] = Instr(2, 8, "ADC A, d8", { proc.adc(emu.readInt(cpu.pc + 1)) }) 232 | op[0xCF] = JmpInstr(1, 16, 16, "RST 08H", { proc.rst(0x08) }) 233 | op[0xD0] = JmpInstr(1, 20, 8, "RET NC", { proc.retNC() }) 234 | op[0xD1] = Instr(1, 12, "POP DE", { proc.popReg(Reg16.DE) }) 235 | op[0xD2] = JmpInstr(3, 16, 12, "JP NC, a16", { proc.jpNC() }) 236 | op[0xD3] = null 237 | op[0xD4] = JmpInstr(3, 24, 12, "CALL NC, a16", { proc.callNC() }) 238 | op[0xD5] = Instr(1, 16, "PUSH DE", { proc.pushReg(Reg16.DE) }) 239 | op[0xD6] = Instr(2, 8, "SUB d8", { proc.sub(emu.readInt(cpu.pc + 1)) }) 240 | op[0xD7] = JmpInstr(1, 16, 16, "RST 10H", { proc.rst(0x10) }) 241 | op[0xD8] = JmpInstr(1, 20, 8, "RET C", { proc.retC() }) 242 | op[0xD9] = JmpInstr(1, 16, 16, "RETI", { proc.reti() }) 243 | op[0xDA] = JmpInstr(3, 16, 12, "JP C, a16", { proc.jpC() }) 244 | op[0xDB] = null 245 | op[0xDC] = JmpInstr(3, 24, 12, "CALL C, a16", { proc.callC() }) 246 | op[0xDD] = null 247 | op[0xDE] = Instr(2, 8, "SBC A, d8", { proc.sbc(emu.readInt(cpu.pc + 1)) }) 248 | op[0xDF] = JmpInstr(1, 16, 16, "RST 18H", { proc.rst(0x18) }) 249 | op[0xE0] = Instr(2, 8, "LDH (a8), A", { 250 | val addr = 0xFF00 + emu.readInt(cpu.pc + 1) 251 | proc.syncTimer(4) 252 | proc.ld8RegToAddr(addr, Reg.A) 253 | }, internalCycles = 4) 254 | op[0xE1] = Instr(1, 12, "POP HL", { proc.popReg(Reg16.HL) }) 255 | op[0xE2] = Instr(1, 8, "LD (C), A", { proc.ld8RegToAddr(0xFF00 + cpu.readRegInt(Reg.C), Reg.A) }) 256 | op[0xE3] = null 257 | op[0xE4] = null 258 | op[0xE5] = Instr(1, 16, "PUSH HL", { proc.pushReg(Reg16.HL) }) 259 | op[0xE6] = Instr(2, 8, "AND d8", { proc.and(emu.readInt(cpu.pc + 1)) }) 260 | op[0xE7] = JmpInstr(1, 16, 16, "RST 20H", { proc.rst(0x20) }) 261 | op[0xE8] = Instr(2, 16, "ADD SP, r8", { 262 | val value = emu.read(cpu.pc + 1).toInt() //r8 is signed here lol 263 | val sp = cpu.sp + value 264 | cpu.resetFlag(Flag.Z) 265 | cpu.resetFlag(Flag.N) 266 | 267 | //the only answer https://stackoverflow.com/questions/5159603/gbz80-how-does-ld-hl-spe-affect-h-and-c-flags 268 | if ((cpu.sp and 0xF) + (value and 0xF) and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 269 | if ((cpu.sp and 0xFF) + (value and 0xFF) and 0x100 != 0) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 270 | 271 | cpu.sp = sp 272 | }) 273 | op[0xE9] = JmpInstr(1, 4, 4, "JP (HL)", { proc.jpHL() }) 274 | op[0xEA] = Instr(3, 8, "LD (a16), A", { proc.ld8RegToImmAddr(Reg.A) }, internalCycles = 8) 275 | op[0xEB] = null 276 | op[0xEC] = null 277 | op[0xED] = null 278 | op[0xEE] = Instr(2, 8, "XOR d8", { proc.xor(emu.readInt(cpu.pc + 1)) }) 279 | op[0xEF] = JmpInstr(1, 16, 16, "RST 28H", { proc.rst(0x28) }) 280 | op[0xF0] = Instr(2, 8, "LDH A, (a8)", { 281 | val addr = 0xFF00 + emu.readInt(cpu.pc + 1) 282 | proc.syncTimer(4) 283 | proc.ld8AddrToReg(Reg.A, addr) 284 | }, internalCycles = 4) 285 | op[0xF1] = Instr(1, 12, "POP AF", { proc.popReg(Reg16.AF) }) 286 | op[0xF2] = Instr(1, 8, "LD A, (C)", { proc.ld8AddrToReg(Reg.A, 0xFF00 + cpu.readRegInt(Reg.C)) }) 287 | op[0xF3] = Instr(1, 4, "DI", { cpu.setImeFlag(false) }) 288 | op[0xF4] = null 289 | op[0xF5] = Instr(1, 16, "PUSH AF", { proc.pushReg(Reg16.AF) }) 290 | op[0xF6] = Instr(2, 8, "OR d8", { proc.or(emu.readInt(cpu.pc + 1)) }) 291 | op[0xF7] = JmpInstr(1, 16, 16, "RST 30H", { proc.rst(0x30) }) 292 | op[0xF8] = Instr(2, 12, "LD HL, SP+r8", { 293 | val value = emu.read(cpu.pc + 1).toInt() 294 | 295 | cpu.resetFlag(Flag.Z) 296 | cpu.resetFlag(Flag.N) 297 | if ((cpu.sp and 0xF) + (value and 0xF) and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 298 | if ((cpu.sp and 0xFF) + (value and 0xFF) and 0x100 != 0) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 299 | 300 | cpu.writeReg(Reg16.HL, cpu.sp + value) 301 | }) 302 | op[0xF9] = Instr(1, 8, "LD SP, HL", { cpu.sp = cpu.readReg(Reg16.HL) }) 303 | op[0xFA] = Instr(3, 8, "LD A, (a16)", { proc.ld8ImmAddrToReg(Reg.A) }, internalCycles = 8) 304 | op[0xFB] = Instr(1, 4, "EI", { cpu.setImeFlag(true) }) 305 | op[0xFC] = null 306 | op[0xFD] = null 307 | op[0xFE] = Instr(2, 8, "CP d8", { proc.cp(emu.readInt(cpu.pc + 1)) }) 308 | op[0xFF] = JmpInstr(1, 16, 16, "RST 38H", { proc.rst(0x38) }) 309 | } 310 | 311 | fun generateExtOpCodes(proc: OpCodesProcessor, op: Array) { 312 | op[0x00] = Instr(2, 8, "RLC B", { proc.rlcReg(Reg.B) }) 313 | op[0x01] = Instr(2, 8, "RLC C", { proc.rlcReg(Reg.C) }) 314 | op[0x02] = Instr(2, 8, "RLC D", { proc.rlcReg(Reg.D) }) 315 | op[0x03] = Instr(2, 8, "RLC E", { proc.rlcReg(Reg.E) }) 316 | op[0x04] = Instr(2, 8, "RLC H", { proc.rlcReg(Reg.H) }) 317 | op[0x05] = Instr(2, 8, "RLC L", { proc.rlcReg(Reg.L) }) 318 | op[0x06] = Instr(2, 8, "RLC (HL)", { proc.syncedHLRegOp({ proc.rlc(it, true) }) }, internalCycles = 8) 319 | op[0x07] = Instr(2, 8, "RLC A", { proc.rlcReg(Reg.A) }) 320 | op[0x08] = Instr(2, 8, "RRC B", { proc.rrcReg(Reg.B) }) 321 | op[0x09] = Instr(2, 8, "RRC C", { proc.rrcReg(Reg.C) }) 322 | op[0x0A] = Instr(2, 8, "RRC D", { proc.rrcReg(Reg.D) }) 323 | op[0x0B] = Instr(2, 8, "RRC E", { proc.rrcReg(Reg.E) }) 324 | op[0x0C] = Instr(2, 8, "RRC H", { proc.rrcReg(Reg.H) }) 325 | op[0x0D] = Instr(2, 8, "RRC L", { proc.rrcReg(Reg.L) }) 326 | op[0x0E] = Instr(2, 8, "RRC (HL)", { proc.syncedHLRegOp({ proc.rrc(it, true) }) }, internalCycles = 8) 327 | op[0x0F] = Instr(2, 8, "RRC A", { proc.rrcReg(Reg.A) }) 328 | op[0x10] = Instr(2, 8, "RL B", { proc.rlReg(Reg.B) }) 329 | op[0x11] = Instr(2, 8, "RL C", { proc.rlReg(Reg.C) }) 330 | op[0x12] = Instr(2, 8, "RL D", { proc.rlReg(Reg.D) }) 331 | op[0x13] = Instr(2, 8, "RL E", { proc.rlReg(Reg.E) }) 332 | op[0x14] = Instr(2, 8, "RL H", { proc.rlReg(Reg.H) }) 333 | op[0x15] = Instr(2, 8, "RL L", { proc.rlReg(Reg.L) }) 334 | op[0x16] = Instr(2, 8, "RL (HL)", { proc.syncedHLRegOp({ proc.rl(it, true) }) }, internalCycles = 8) 335 | op[0x17] = Instr(2, 8, "RL A", { proc.rlReg(Reg.A) }) 336 | op[0x18] = Instr(2, 8, "RR B", { proc.rrReg(Reg.B) }) 337 | op[0x19] = Instr(2, 8, "RR C", { proc.rrReg(Reg.C) }) 338 | op[0x1A] = Instr(2, 8, "RR D", { proc.rrReg(Reg.D) }) 339 | op[0x1B] = Instr(2, 8, "RR E", { proc.rrReg(Reg.E) }) 340 | op[0x1C] = Instr(2, 8, "RR H", { proc.rrReg(Reg.H) }) 341 | op[0x1D] = Instr(2, 8, "RR L", { proc.rrReg(Reg.L) }) 342 | op[0x1E] = Instr(2, 8, "RR (HL)", { proc.syncedHLRegOp({ proc.rr(it, true) }) }, internalCycles = 8) 343 | op[0x1F] = Instr(2, 8, "RR A", { proc.rrReg(Reg.A) }) 344 | op[0x20] = Instr(2, 8, "SLA B", { proc.slaReg(Reg.B) }) 345 | op[0x21] = Instr(2, 8, "SLA C", { proc.slaReg(Reg.C) }) 346 | op[0x22] = Instr(2, 8, "SLA D", { proc.slaReg(Reg.D) }) 347 | op[0x23] = Instr(2, 8, "SLA E", { proc.slaReg(Reg.E) }) 348 | op[0x24] = Instr(2, 8, "SLA H", { proc.slaReg(Reg.H) }) 349 | op[0x25] = Instr(2, 8, "SLA L", { proc.slaReg(Reg.L) }) 350 | op[0x26] = Instr(2, 8, "SLA (HL)", { proc.syncedHLRegOp({ proc.sla(it) }) }, internalCycles = 8) 351 | op[0x27] = Instr(2, 8, "SLA A", { proc.slaReg(Reg.A) }) 352 | op[0x28] = Instr(2, 8, "SRA B", { proc.sraReg(Reg.B) }) 353 | op[0x29] = Instr(2, 8, "SRA C", { proc.sraReg(Reg.C) }) 354 | op[0x2A] = Instr(2, 8, "SRA D", { proc.sraReg(Reg.D) }) 355 | op[0x2B] = Instr(2, 8, "SRA E", { proc.sraReg(Reg.E) }) 356 | op[0x2C] = Instr(2, 8, "SRA H", { proc.sraReg(Reg.H) }) 357 | op[0x2D] = Instr(2, 8, "SRA L", { proc.sraReg(Reg.L) }) 358 | op[0x2E] = Instr(2, 8, "SRA (HL)", { proc.syncedHLRegOp({ proc.sra(it) }) }, internalCycles = 8) 359 | op[0x2F] = Instr(2, 8, "SRA A", { proc.sraReg(Reg.A) }) 360 | op[0x30] = Instr(2, 8, "SWAP B", { proc.swapReg(Reg.B) }) 361 | op[0x31] = Instr(2, 8, "SWAP C", { proc.swapReg(Reg.C) }) 362 | op[0x32] = Instr(2, 8, "SWAP D", { proc.swapReg(Reg.D) }) 363 | op[0x33] = Instr(2, 8, "SWAP E", { proc.swapReg(Reg.E) }) 364 | op[0x34] = Instr(2, 8, "SWAP H", { proc.swapReg(Reg.H) }) 365 | op[0x35] = Instr(2, 8, "SWAP L", { proc.swapReg(Reg.L) }) 366 | op[0x36] = Instr(2, 8, "SWAP (HL)", { proc.syncedHLRegOp({ proc.swap(it) }) }, internalCycles = 8) 367 | op[0x37] = Instr(2, 8, "SWAP A", { proc.swapReg(Reg.A) }) 368 | op[0x38] = Instr(2, 8, "SRL B", { proc.srlReg(Reg.B) }) 369 | op[0x39] = Instr(2, 8, "SRL C", { proc.srlReg(Reg.C) }) 370 | op[0x3A] = Instr(2, 8, "SRL D", { proc.srlReg(Reg.D) }) 371 | op[0x3B] = Instr(2, 8, "SRL E", { proc.srlReg(Reg.E) }) 372 | op[0x3C] = Instr(2, 8, "SRL H", { proc.srlReg(Reg.H) }) 373 | op[0x3D] = Instr(2, 8, "SRL L", { proc.srlReg(Reg.L) }) 374 | op[0x3E] = Instr(2, 8, "SRL (HL)", { proc.syncedHLRegOp({ proc.srl(it) }) }, internalCycles = 8) 375 | op[0x3F] = Instr(2, 8, "SRL A", { proc.srlReg(Reg.A) }) 376 | op[0x40] = Instr(2, 8, "BIT 0, B", { proc.bitReg(0, Reg.B) }) 377 | op[0x41] = Instr(2, 8, "BIT 0, C", { proc.bitReg(0, Reg.C) }) 378 | op[0x42] = Instr(2, 8, "BIT 0, D", { proc.bitReg(0, Reg.D) }) 379 | op[0x43] = Instr(2, 8, "BIT 0, E", { proc.bitReg(0, Reg.E) }) 380 | op[0x44] = Instr(2, 8, "BIT 0, H", { proc.bitReg(0, Reg.H) }) 381 | op[0x45] = Instr(2, 8, "BIT 0, L", { proc.bitReg(0, Reg.L) }) 382 | op[0x46] = Instr(2, 8, "BIT 0, (HL)", { proc.bitHL(0) }, internalCycles = 4) 383 | op[0x47] = Instr(2, 8, "BIT 0, A", { proc.bitReg(0, Reg.A) }) 384 | op[0x48] = Instr(2, 8, "BIT 1, B", { proc.bitReg(1, Reg.B) }) 385 | op[0x49] = Instr(2, 8, "BIT 1, C", { proc.bitReg(1, Reg.C) }) 386 | op[0x4A] = Instr(2, 8, "BIT 1, D", { proc.bitReg(1, Reg.D) }) 387 | op[0x4B] = Instr(2, 8, "BIT 1, E", { proc.bitReg(1, Reg.E) }) 388 | op[0x4C] = Instr(2, 8, "BIT 1, H", { proc.bitReg(1, Reg.H) }) 389 | op[0x4D] = Instr(2, 8, "BIT 1, L", { proc.bitReg(1, Reg.L) }) 390 | op[0x4E] = Instr(2, 8, "BIT 1, (HL)", { proc.bitHL(1) }, internalCycles = 4) 391 | op[0x4F] = Instr(2, 8, "BIT 1, A", { proc.bitReg(1, Reg.A) }) 392 | op[0x50] = Instr(2, 8, "BIT 2, B", { proc.bitReg(2, Reg.B) }) 393 | op[0x51] = Instr(2, 8, "BIT 2, C", { proc.bitReg(2, Reg.C) }) 394 | op[0x52] = Instr(2, 8, "BIT 2, D", { proc.bitReg(2, Reg.D) }) 395 | op[0x53] = Instr(2, 8, "BIT 2, E", { proc.bitReg(2, Reg.E) }) 396 | op[0x54] = Instr(2, 8, "BIT 2, H", { proc.bitReg(2, Reg.H) }) 397 | op[0x55] = Instr(2, 8, "BIT 2, L", { proc.bitReg(2, Reg.L) }) 398 | op[0x56] = Instr(2, 8, "BIT 2, (HL)", { proc.bitHL(2) }, internalCycles = 4) 399 | op[0x57] = Instr(2, 8, "BIT 2, A", { proc.bitReg(2, Reg.A) }) 400 | op[0x58] = Instr(2, 8, "BIT 3, B", { proc.bitReg(3, Reg.B) }) 401 | op[0x59] = Instr(2, 8, "BIT 3, C", { proc.bitReg(3, Reg.C) }) 402 | op[0x5A] = Instr(2, 8, "BIT 3, D", { proc.bitReg(3, Reg.D) }) 403 | op[0x5B] = Instr(2, 8, "BIT 3, E", { proc.bitReg(3, Reg.E) }) 404 | op[0x5C] = Instr(2, 8, "BIT 3, H", { proc.bitReg(3, Reg.H) }) 405 | op[0x5D] = Instr(2, 8, "BIT 3, L", { proc.bitReg(3, Reg.L) }) 406 | op[0x5E] = Instr(2, 8, "BIT 3, (HL)", { proc.bitHL(3) }, internalCycles = 4) 407 | op[0x5F] = Instr(2, 8, "BIT 3, A", { proc.bitReg(3, Reg.A) }) 408 | op[0x60] = Instr(2, 8, "BIT 4, B", { proc.bitReg(4, Reg.B) }) 409 | op[0x61] = Instr(2, 8, "BIT 4, C", { proc.bitReg(4, Reg.C) }) 410 | op[0x62] = Instr(2, 8, "BIT 4, D", { proc.bitReg(4, Reg.D) }) 411 | op[0x63] = Instr(2, 8, "BIT 4, E", { proc.bitReg(4, Reg.E) }) 412 | op[0x64] = Instr(2, 8, "BIT 4, H", { proc.bitReg(4, Reg.H) }) 413 | op[0x65] = Instr(2, 8, "BIT 4, L", { proc.bitReg(4, Reg.L) }) 414 | op[0x66] = Instr(2, 8, "BIT 4, (HL)", { proc.bitHL(4) }, internalCycles = 4) 415 | op[0x67] = Instr(2, 8, "BIT 4, A", { proc.bitReg(4, Reg.A) }) 416 | op[0x68] = Instr(2, 8, "BIT 5, B", { proc.bitReg(5, Reg.B) }) 417 | op[0x69] = Instr(2, 8, "BIT 5, C", { proc.bitReg(5, Reg.C) }) 418 | op[0x6A] = Instr(2, 8, "BIT 5, D", { proc.bitReg(5, Reg.D) }) 419 | op[0x6B] = Instr(2, 8, "BIT 5, E", { proc.bitReg(5, Reg.E) }) 420 | op[0x6C] = Instr(2, 8, "BIT 5, H", { proc.bitReg(5, Reg.H) }) 421 | op[0x6D] = Instr(2, 8, "BIT 5, L", { proc.bitReg(5, Reg.L) }) 422 | op[0x6E] = Instr(2, 8, "BIT 5, (HL)", { proc.bitHL(5) }, internalCycles = 4) 423 | op[0x6F] = Instr(2, 8, "BIT 5, A", { proc.bitReg(5, Reg.A) }) 424 | op[0x70] = Instr(2, 8, "BIT 6, B", { proc.bitReg(6, Reg.B) }) 425 | op[0x71] = Instr(2, 8, "BIT 6, C", { proc.bitReg(6, Reg.C) }) 426 | op[0x72] = Instr(2, 8, "BIT 6, D", { proc.bitReg(6, Reg.D) }) 427 | op[0x73] = Instr(2, 8, "BIT 6, E", { proc.bitReg(6, Reg.E) }) 428 | op[0x74] = Instr(2, 8, "BIT 6, H", { proc.bitReg(6, Reg.H) }) 429 | op[0x75] = Instr(2, 8, "BIT 6, L", { proc.bitReg(6, Reg.L) }) 430 | op[0x76] = Instr(2, 8, "BIT 6, (HL)", { proc.bitHL(6) }, internalCycles = 4) 431 | op[0x77] = Instr(2, 8, "BIT 6, A", { proc.bitReg(6, Reg.A) }) 432 | op[0x78] = Instr(2, 8, "BIT 7, B", { proc.bitReg(7, Reg.B) }) 433 | op[0x79] = Instr(2, 8, "BIT 7, C", { proc.bitReg(7, Reg.C) }) 434 | op[0x7A] = Instr(2, 8, "BIT 7, D", { proc.bitReg(7, Reg.D) }) 435 | op[0x7B] = Instr(2, 8, "BIT 7, E", { proc.bitReg(7, Reg.E) }) 436 | op[0x7C] = Instr(2, 8, "BIT 7, H", { proc.bitReg(7, Reg.H) }) 437 | op[0x7D] = Instr(2, 8, "BIT 7, L", { proc.bitReg(7, Reg.L) }) 438 | op[0x7E] = Instr(2, 8, "BIT 7, (HL)", { proc.bitHL(7) }, internalCycles = 4) 439 | op[0x7F] = Instr(2, 8, "BIT 7, A", { proc.bitReg(7, Reg.A) }) 440 | op[0x80] = Instr(2, 8, "RES 0, B", { proc.resReg(0, Reg.B) }) 441 | op[0x81] = Instr(2, 8, "RES 0, C", { proc.resReg(0, Reg.C) }) 442 | op[0x82] = Instr(2, 8, "RES 0, D", { proc.resReg(0, Reg.D) }) 443 | op[0x83] = Instr(2, 8, "RES 0, E", { proc.resReg(0, Reg.E) }) 444 | op[0x84] = Instr(2, 8, "RES 0, H", { proc.resReg(0, Reg.H) }) 445 | op[0x85] = Instr(2, 8, "RES 0, L", { proc.resReg(0, Reg.L) }) 446 | op[0x86] = Instr(2, 8, "RES 0, (HL)", { proc.syncedHLRegOp({ proc.res(0, it) }) }, internalCycles = 8) 447 | op[0x87] = Instr(2, 8, "RES 0, A", { proc.resReg(0, Reg.A) }) 448 | op[0x88] = Instr(2, 8, "RES 1, B", { proc.resReg(1, Reg.B) }) 449 | op[0x89] = Instr(2, 8, "RES 1, C", { proc.resReg(1, Reg.C) }) 450 | op[0x8A] = Instr(2, 8, "RES 1, D", { proc.resReg(1, Reg.D) }) 451 | op[0x8B] = Instr(2, 8, "RES 1, E", { proc.resReg(1, Reg.E) }) 452 | op[0x8C] = Instr(2, 8, "RES 1, H", { proc.resReg(1, Reg.H) }) 453 | op[0x8D] = Instr(2, 8, "RES 1, L", { proc.resReg(1, Reg.L) }) 454 | op[0x8E] = Instr(2, 8, "RES 1, (HL)", { proc.syncedHLRegOp({ proc.res(1, it) }) }, internalCycles = 8) 455 | op[0x8F] = Instr(2, 8, "RES 1, A", { proc.resReg(1, Reg.A) }) 456 | op[0x90] = Instr(2, 8, "RES 2, B", { proc.resReg(2, Reg.B) }) 457 | op[0x91] = Instr(2, 8, "RES 2, C", { proc.resReg(2, Reg.C) }) 458 | op[0x92] = Instr(2, 8, "RES 2, D", { proc.resReg(2, Reg.D) }) 459 | op[0x93] = Instr(2, 8, "RES 2, E", { proc.resReg(2, Reg.E) }) 460 | op[0x94] = Instr(2, 8, "RES 2, H", { proc.resReg(2, Reg.H) }) 461 | op[0x95] = Instr(2, 8, "RES 2, L", { proc.resReg(2, Reg.L) }) 462 | op[0x96] = Instr(2, 8, "RES 2, (HL)", { proc.syncedHLRegOp({ proc.res(2, it) }) }, internalCycles = 8) 463 | op[0x97] = Instr(2, 8, "RES 2, A", { proc.resReg(2, Reg.A) }) 464 | op[0x98] = Instr(2, 8, "RES 3, B", { proc.resReg(3, Reg.B) }) 465 | op[0x99] = Instr(2, 8, "RES 3, C", { proc.resReg(3, Reg.C) }) 466 | op[0x9A] = Instr(2, 8, "RES 3, D", { proc.resReg(3, Reg.D) }) 467 | op[0x9B] = Instr(2, 8, "RES 3, E", { proc.resReg(3, Reg.E) }) 468 | op[0x9C] = Instr(2, 8, "RES 3, H", { proc.resReg(3, Reg.H) }) 469 | op[0x9D] = Instr(2, 8, "RES 3, L", { proc.resReg(3, Reg.L) }) 470 | op[0x9E] = Instr(2, 8, "RES 3, (HL)", { proc.syncedHLRegOp({ proc.res(3, it) }) }, internalCycles = 8) 471 | op[0x9F] = Instr(2, 8, "RES 3, A", { proc.resReg(3, Reg.A) }) 472 | op[0xA0] = Instr(2, 8, "RES 4, B", { proc.resReg(4, Reg.B) }) 473 | op[0xA1] = Instr(2, 8, "RES 4, C", { proc.resReg(4, Reg.C) }) 474 | op[0xA2] = Instr(2, 8, "RES 4, D", { proc.resReg(4, Reg.D) }) 475 | op[0xA3] = Instr(2, 8, "RES 4, E", { proc.resReg(4, Reg.E) }) 476 | op[0xA4] = Instr(2, 8, "RES 4, H", { proc.resReg(4, Reg.H) }) 477 | op[0xA5] = Instr(2, 8, "RES 4, L", { proc.resReg(4, Reg.L) }) 478 | op[0xA6] = Instr(2, 8, "RES 4, (HL)", { proc.syncedHLRegOp({ proc.res(4, it) }) }, internalCycles = 8) 479 | op[0xA7] = Instr(2, 8, "RES 4, A", { proc.resReg(4, Reg.A) }) 480 | op[0xA8] = Instr(2, 8, "RES 5, B", { proc.resReg(5, Reg.B) }) 481 | op[0xA9] = Instr(2, 8, "RES 5, C", { proc.resReg(5, Reg.C) }) 482 | op[0xAA] = Instr(2, 8, "RES 5, D", { proc.resReg(5, Reg.D) }) 483 | op[0xAB] = Instr(2, 8, "RES 5, E", { proc.resReg(5, Reg.E) }) 484 | op[0xAC] = Instr(2, 8, "RES 5, H", { proc.resReg(5, Reg.H) }) 485 | op[0xAD] = Instr(2, 8, "RES 5, L", { proc.resReg(5, Reg.L) }) 486 | op[0xAE] = Instr(2, 8, "RES 5, (HL)", { proc.syncedHLRegOp({ proc.res(5, it) }) }, internalCycles = 8) 487 | op[0xAF] = Instr(2, 8, "RES 5, A", { proc.resReg(5, Reg.A) }) 488 | op[0xB0] = Instr(2, 8, "RES 6, B", { proc.resReg(6, Reg.B) }) 489 | op[0xB1] = Instr(2, 8, "RES 6, C", { proc.resReg(6, Reg.C) }) 490 | op[0xB2] = Instr(2, 8, "RES 6, D", { proc.resReg(6, Reg.D) }) 491 | op[0xB3] = Instr(2, 8, "RES 6, E", { proc.resReg(6, Reg.E) }) 492 | op[0xB4] = Instr(2, 8, "RES 6, H", { proc.resReg(6, Reg.H) }) 493 | op[0xB5] = Instr(2, 8, "RES 6, L", { proc.resReg(6, Reg.L) }) 494 | op[0xB6] = Instr(2, 8, "RES 6, (HL)", { proc.syncedHLRegOp({ proc.res(6, it) }) }, internalCycles = 8) 495 | op[0xB7] = Instr(2, 8, "RES 6, A", { proc.resReg(6, Reg.A) }) 496 | op[0xB8] = Instr(2, 8, "RES 7, B", { proc.resReg(7, Reg.B) }) 497 | op[0xB9] = Instr(2, 8, "RES 7, C", { proc.resReg(7, Reg.C) }) 498 | op[0xBA] = Instr(2, 8, "RES 7, D", { proc.resReg(7, Reg.D) }) 499 | op[0xBB] = Instr(2, 8, "RES 7, E", { proc.resReg(7, Reg.E) }) 500 | op[0xBC] = Instr(2, 8, "RES 7, H", { proc.resReg(7, Reg.H) }) 501 | op[0xBD] = Instr(2, 8, "RES 7, L", { proc.resReg(7, Reg.L) }) 502 | op[0xBE] = Instr(2, 8, "RES 7, (HL)", { proc.syncedHLRegOp({ proc.res(7, it) }) }, internalCycles = 8) 503 | op[0xBF] = Instr(2, 8, "RES 7, A", { proc.resReg(7, Reg.A) }) 504 | op[0xC0] = Instr(2, 8, "SET 0, B", { proc.setReg(0, Reg.B) }) 505 | op[0xC1] = Instr(2, 8, "SET 0, C", { proc.setReg(0, Reg.C) }) 506 | op[0xC2] = Instr(2, 8, "SET 0, D", { proc.setReg(0, Reg.D) }) 507 | op[0xC3] = Instr(2, 8, "SET 0, E", { proc.setReg(0, Reg.E) }) 508 | op[0xC4] = Instr(2, 8, "SET 0, H", { proc.setReg(0, Reg.H) }) 509 | op[0xC5] = Instr(2, 8, "SET 0, L", { proc.setReg(0, Reg.L) }) 510 | op[0xC6] = Instr(2, 8, "SET 0, (HL)", { proc.syncedHLRegOp({ proc.set(0, it) }) }, internalCycles = 8) 511 | op[0xC7] = Instr(2, 8, "SET 0, A", { proc.setReg(0, Reg.A) }) 512 | op[0xC8] = Instr(2, 8, "SET 1, B", { proc.setReg(1, Reg.B) }) 513 | op[0xC9] = Instr(2, 8, "SET 1, C", { proc.setReg(1, Reg.C) }) 514 | op[0xCA] = Instr(2, 8, "SET 1, D", { proc.setReg(1, Reg.D) }) 515 | op[0xCB] = Instr(2, 8, "SET 1, E", { proc.setReg(1, Reg.E) }) 516 | op[0xCC] = Instr(2, 8, "SET 1, H", { proc.setReg(1, Reg.H) }) 517 | op[0xCD] = Instr(2, 8, "SET 1, L", { proc.setReg(1, Reg.L) }) 518 | op[0xCE] = Instr(2, 8, "SET 1, (HL)", { proc.syncedHLRegOp({ proc.set(1, it) }) }, internalCycles = 8) 519 | op[0xCF] = Instr(2, 8, "SET 1, A", { proc.setReg(1, Reg.A) }) 520 | op[0xD0] = Instr(2, 8, "SET 2, B", { proc.setReg(2, Reg.B) }) 521 | op[0xD1] = Instr(2, 8, "SET 2, C", { proc.setReg(2, Reg.C) }) 522 | op[0xD2] = Instr(2, 8, "SET 2, D", { proc.setReg(2, Reg.D) }) 523 | op[0xD3] = Instr(2, 8, "SET 2, E", { proc.setReg(2, Reg.E) }) 524 | op[0xD4] = Instr(2, 8, "SET 2, H", { proc.setReg(2, Reg.H) }) 525 | op[0xD5] = Instr(2, 8, "SET 2, L", { proc.setReg(2, Reg.L) }) 526 | op[0xD6] = Instr(2, 8, "SET 2, (HL)", { proc.syncedHLRegOp({ proc.set(2, it) }) }, internalCycles = 8) 527 | op[0xD7] = Instr(2, 8, "SET 2, A", { proc.setReg(2, Reg.A) }) 528 | op[0xD8] = Instr(2, 8, "SET 3, B", { proc.setReg(3, Reg.B) }) 529 | op[0xD9] = Instr(2, 8, "SET 3, C", { proc.setReg(3, Reg.C) }) 530 | op[0xDA] = Instr(2, 8, "SET 3, D", { proc.setReg(3, Reg.D) }) 531 | op[0xDB] = Instr(2, 8, "SET 3, E", { proc.setReg(3, Reg.E) }) 532 | op[0xDC] = Instr(2, 8, "SET 3, H", { proc.setReg(3, Reg.H) }) 533 | op[0xDD] = Instr(2, 8, "SET 3, L", { proc.setReg(3, Reg.L) }) 534 | op[0xDE] = Instr(2, 8, "SET 3, (HL)", { proc.syncedHLRegOp({ proc.set(3, it) }) }, internalCycles = 8) 535 | op[0xDF] = Instr(2, 8, "SET 3, A", { proc.setReg(3, Reg.A) }) 536 | op[0xE0] = Instr(2, 8, "SET 4, B", { proc.setReg(4, Reg.B) }) 537 | op[0xE1] = Instr(2, 8, "SET 4, C", { proc.setReg(4, Reg.C) }) 538 | op[0xE2] = Instr(2, 8, "SET 4, D", { proc.setReg(4, Reg.D) }) 539 | op[0xE3] = Instr(2, 8, "SET 4, E", { proc.setReg(4, Reg.E) }) 540 | op[0xE4] = Instr(2, 8, "SET 4, H", { proc.setReg(4, Reg.H) }) 541 | op[0xE5] = Instr(2, 8, "SET 4, L", { proc.setReg(4, Reg.L) }) 542 | op[0xE6] = Instr(2, 8, "SET 4, (HL)", { proc.syncedHLRegOp({ proc.set(4, it) }) }, internalCycles = 8) 543 | op[0xE7] = Instr(2, 8, "SET 4, A", { proc.setReg(4, Reg.A) }) 544 | op[0xE8] = Instr(2, 8, "SET 5, B", { proc.setReg(5, Reg.B) }) 545 | op[0xE9] = Instr(2, 8, "SET 5, C", { proc.setReg(5, Reg.C) }) 546 | op[0xEA] = Instr(2, 8, "SET 5, D", { proc.setReg(5, Reg.D) }) 547 | op[0xEB] = Instr(2, 8, "SET 5, E", { proc.setReg(5, Reg.E) }) 548 | op[0xEC] = Instr(2, 8, "SET 5, H", { proc.setReg(5, Reg.H) }) 549 | op[0xED] = Instr(2, 8, "SET 5, L", { proc.setReg(5, Reg.L) }) 550 | op[0xEE] = Instr(2, 8, "SET 5, (HL)", { proc.syncedHLRegOp({ proc.set(5, it) }) }, internalCycles = 8) 551 | op[0xEF] = Instr(2, 8, "SET 5, A", { proc.setReg(5, Reg.A) }) 552 | op[0xF0] = Instr(2, 8, "SET 6, B", { proc.setReg(6, Reg.B) }) 553 | op[0xF1] = Instr(2, 8, "SET 6, C", { proc.setReg(6, Reg.C) }) 554 | op[0xF2] = Instr(2, 8, "SET 6, D", { proc.setReg(6, Reg.D) }) 555 | op[0xF3] = Instr(2, 8, "SET 6, E", { proc.setReg(6, Reg.E) }) 556 | op[0xF4] = Instr(2, 8, "SET 6, H", { proc.setReg(6, Reg.H) }) 557 | op[0xF5] = Instr(2, 8, "SET 6, L", { proc.setReg(6, Reg.L) }) 558 | op[0xF6] = Instr(2, 8, "SET 6, (HL)", { proc.syncedHLRegOp({ proc.set(6, it) }) }, internalCycles = 8) 559 | op[0xF7] = Instr(2, 8, "SET 6, A", { proc.setReg(6, Reg.A) }) 560 | op[0xF8] = Instr(2, 8, "SET 7, B", { proc.setReg(7, Reg.B) }) 561 | op[0xF9] = Instr(2, 8, "SET 7, C", { proc.setReg(7, Reg.C) }) 562 | op[0xFA] = Instr(2, 8, "SET 7, D", { proc.setReg(7, Reg.D) }) 563 | op[0xFB] = Instr(2, 8, "SET 7, E", { proc.setReg(7, Reg.E) }) 564 | op[0xFC] = Instr(2, 8, "SET 7, H", { proc.setReg(7, Reg.H) }) 565 | op[0xFD] = Instr(2, 8, "SET 7, L", { proc.setReg(7, Reg.L) }) 566 | op[0xFE] = Instr(2, 8, "SET 7, (HL)", { proc.syncedHLRegOp({ proc.set(7, it) }) }, internalCycles = 8) 567 | op[0xFF] = Instr(2, 8, "SET 7, A", { proc.setReg(7, Reg.A) }) 568 | } 569 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/cpu/OpCodesProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.cpu 2 | 3 | import com.kotcrab.xgbc.* 4 | 5 | /** @author Kotcrab */ 6 | class OpCodesProcessor(private val emulator: Emulator, private val cpu: Cpu) { 7 | // 8 bit load operations 8 | 9 | /** Copies value from cpu's reg2 into reg1. */ 10 | fun ld(reg1: Reg, reg2: Reg) { 11 | cpu.writeReg(reg1, cpu.readReg(reg2)) 12 | } 13 | 14 | /** Loads 8 bit value from memory address pointed by reg16 into reg */ 15 | fun ld8Reg16AddrToReg(reg: Reg, reg16: Reg16) { 16 | cpu.writeReg(reg, emulator.read(cpu.readReg(reg16))) 17 | } 18 | 19 | /** Saves 8 bit value from reg into memory address pointed by reg16 */ 20 | fun ld8RegToReg16Addr(reg16: Reg16, reg: Reg) { 21 | emulator.write(cpu.readReg(reg16), cpu.readReg(reg)) 22 | } 23 | 24 | /** Loads 8 bit value from addr into reg */ 25 | fun ld8AddrToReg(reg: Reg, addr: Int) { 26 | cpu.writeReg(reg, emulator.read(addr)) 27 | } 28 | 29 | /** Saves 8 bit value from reg to addr */ 30 | fun ld8RegToAddr(addr: Int, reg: Reg) { 31 | emulator.write(addr, cpu.readReg(reg)) 32 | } 33 | 34 | // Immediate 8 bit operations 35 | 36 | /** Loads immediate 8 bit value into cpu register. */ 37 | fun ld8ImmValueToReg(reg: Reg) { 38 | cpu.writeReg(reg, emulator.read(cpu.pc + 1)) 39 | } 40 | 41 | /** Loads 8 bit value from immediate address into reg */ 42 | fun ld8ImmAddrToReg(reg: Reg) { 43 | val fromAddr = emulator.read16(cpu.pc + 1) 44 | syncTimer(8) 45 | ld8AddrToReg(reg, fromAddr) 46 | } 47 | 48 | /** Saves 8 bit value from reg into immediate address */ 49 | fun ld8RegToImmAddr(reg: Reg) { 50 | val toAddr = emulator.read16(cpu.pc + 1) 51 | syncTimer(8) 52 | ld8RegToAddr(toAddr, reg) 53 | } 54 | 55 | // 16 bit load operations 56 | 57 | fun ld16ValueToReg16(reg16: Reg16, value: Int) { 58 | cpu.writeReg(reg16, value) 59 | } 60 | 61 | // Immediate 16 bit operations 62 | 63 | fun ld16ImmValueToReg(reg16: Reg16) { 64 | ld16ValueToReg16(reg16, emulator.read16(cpu.pc + 1)) 65 | } 66 | 67 | // 8 bit ALU 68 | 69 | fun add(value: Int) { 70 | val regA = cpu.readRegInt(Reg.A) 71 | val result = regA + value 72 | 73 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 74 | cpu.resetFlag(Flag.N) 75 | if ((regA and 0xF) + (value and 0xF) and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 76 | if ((regA and 0xFF) + (value and 0xFF) and 0x100 != 0) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 77 | 78 | cpu.writeReg(Reg.A, result.toByte()) 79 | } 80 | 81 | fun addReg(reg: Reg) { 82 | add(cpu.readRegInt(reg)) 83 | } 84 | 85 | fun adc(value: Int) { 86 | val regA = cpu.readRegInt(Reg.A) 87 | val carry = cpu.isFlagSet(Flag.C).toInt() 88 | val result = regA + value + carry 89 | 90 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 91 | cpu.resetFlag(Flag.N) 92 | if ((regA and 0xF) + (value and 0xF) + carry and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 93 | if ((regA and 0xFF) + (value and 0xFF) + carry and 0x100 != 0) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 94 | 95 | cpu.writeReg(Reg.A, result.toByte()) 96 | } 97 | 98 | fun adcReg(reg: Reg) { 99 | adc(cpu.readRegInt(reg)) 100 | } 101 | 102 | fun sub(value: Int) { 103 | val regA = cpu.readRegInt(Reg.A) 104 | val result = regA - value 105 | 106 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 107 | cpu.setFlag(Flag.N) 108 | if ((regA and 0xF) - (value and 0xF) and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 109 | if ((regA and 0xFF) - (value and 0xFF) and 0x100 != 0) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 110 | 111 | cpu.writeReg(Reg.A, result.toByte()) 112 | } 113 | 114 | fun subReg(reg: Reg) { 115 | sub(cpu.readRegInt(reg)) 116 | } 117 | 118 | fun sbc(value: Int) { 119 | val regA = cpu.readRegInt(Reg.A) 120 | val carry = cpu.isFlagSet(Flag.C).toInt() 121 | val result = regA - value - carry 122 | 123 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 124 | cpu.setFlag(Flag.N) 125 | if ((regA and 0xF) - (value and 0xF) - carry and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 126 | if ((regA and 0xFF) - (value and 0xFF) - carry and 0x100 != 0) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 127 | 128 | cpu.writeReg(Reg.A, result.toByte()) 129 | } 130 | 131 | fun sbcReg(reg: Reg) { 132 | sbc(cpu.readRegInt(reg)) 133 | } 134 | 135 | fun and(value: Int) { 136 | val regA = cpu.readRegInt(Reg.A) 137 | val result = regA and value 138 | if (result == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 139 | cpu.resetFlag(Flag.N) 140 | cpu.setFlag(Flag.H) 141 | cpu.resetFlag(Flag.C) 142 | 143 | cpu.writeReg(Reg.A, result.toByte()) 144 | } 145 | 146 | fun andReg(reg: Reg) { 147 | and(cpu.readRegInt(reg)) 148 | } 149 | 150 | fun or(value: Int) { 151 | val regA = cpu.readRegInt(Reg.A) 152 | val result = regA or value 153 | if (result == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 154 | cpu.resetFlag(Flag.N) 155 | cpu.resetFlag(Flag.H) 156 | cpu.resetFlag(Flag.C) 157 | 158 | cpu.writeReg(Reg.A, result.toByte()) 159 | } 160 | 161 | fun orReg(reg: Reg) { 162 | or(cpu.readRegInt(reg)) 163 | } 164 | 165 | fun xor(value: Int) { 166 | val regA = cpu.readRegInt(Reg.A) 167 | val result = regA xor value 168 | if (result == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 169 | cpu.resetFlag(Flag.N) 170 | cpu.resetFlag(Flag.H) 171 | cpu.resetFlag(Flag.C) 172 | 173 | cpu.writeReg(Reg.A, result.toByte()) 174 | } 175 | 176 | fun xorReg(reg: Reg) { 177 | xor(cpu.readRegInt(reg)) 178 | } 179 | 180 | fun cp(value: Int) { 181 | val regA = cpu.readRegInt(Reg.A) 182 | val result = regA - value 183 | 184 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 185 | cpu.setFlag(Flag.N) 186 | if ((regA and 0xF) - (value and 0xF) and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 187 | if (regA < value) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 188 | } 189 | 190 | fun cpReg(reg: Reg) { 191 | cp(cpu.readRegInt(reg)) 192 | } 193 | 194 | fun inc(reg: Reg) { 195 | val regValue = cpu.readRegInt(reg) 196 | val result = regValue + 1 197 | 198 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 199 | cpu.resetFlag(Flag.N) 200 | if ((regValue and 0xF) + (1 and 0xF) and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 201 | 202 | cpu.writeReg(reg, result.toByte()) 203 | } 204 | 205 | fun incHL() { 206 | val addr = cpu.readReg(Reg16.HL) 207 | val value = emulator.readInt(addr) 208 | syncTimer(4) 209 | val result = value + 1 210 | 211 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 212 | cpu.resetFlag(Flag.N) 213 | if ((value and 0xF) + (1 and 0xF) and 0x10 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 214 | 215 | emulator.write(addr, result.toByte()) 216 | } 217 | 218 | fun dec(reg: Reg) { 219 | val regValue = cpu.readRegInt(reg) 220 | val result = regValue - 1 221 | 222 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 223 | cpu.setFlag(Flag.N) 224 | if ((regValue and 0xF) + (-1 and 0xF) and 0x10 != 0) cpu.resetFlag(Flag.H) else cpu.setFlag(Flag.H) 225 | 226 | cpu.writeReg(reg, result.toByte()) 227 | } 228 | 229 | fun decHL() { 230 | val addr = cpu.readReg(Reg16.HL) 231 | val value = emulator.readInt(addr) 232 | syncTimer(4) 233 | val result = value - 1 234 | 235 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 236 | cpu.setFlag(Flag.N) 237 | if ((value and 0xF) + (-1 and 0xF) and 0x10 != 0) cpu.resetFlag(Flag.H) else cpu.setFlag(Flag.H) 238 | 239 | emulator.write(addr, result.toByte()) 240 | } 241 | 242 | // 16 bit ALU 243 | 244 | fun addHL(reg16: Reg16) { 245 | val regHL = cpu.readReg(Reg16.HL) 246 | val reg = cpu.readReg(reg16) 247 | val result = regHL + reg 248 | 249 | cpu.resetFlag(Flag.N) 250 | if ((regHL and 0xFFF) + (reg and 0xFFF) and 0x1000 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 251 | if ((regHL and 0xFFFF) + (reg and 0xFFFF) and 0x10000 != 0) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 252 | 253 | cpu.writeReg(Reg16.HL, result) 254 | } 255 | 256 | fun addHLSP() { 257 | val regHL = cpu.readReg(Reg16.HL) 258 | val result = regHL + cpu.sp 259 | 260 | cpu.resetFlag(Flag.N) 261 | if ((regHL and 0xFFF) + (cpu.sp and 0xFFF) and 0x1000 != 0) cpu.setFlag(Flag.H) else cpu.resetFlag(Flag.H) 262 | if ((regHL and 0xFFFF) + (cpu.sp and 0xFFFF) and 0x10000 != 0) cpu.setFlag(Flag.C) else cpu.resetFlag(Flag.C) 263 | 264 | cpu.writeReg(Reg16.HL, result) 265 | } 266 | 267 | /** Decrements reg16 */ 268 | fun dec16(reg16: Reg16) { 269 | val value = cpu.readReg(reg16) 270 | cpu.writeReg(reg16, value - 1) 271 | } 272 | 273 | /** Increments reg16 */ 274 | fun inc16(reg16: Reg16) { 275 | val value = cpu.readReg(reg16) 276 | cpu.writeReg(reg16, value + 1) 277 | } 278 | 279 | // Stack pointer push and pop 280 | 281 | fun push(addr: Int) { 282 | cpu.sp = cpu.sp - 2 283 | emulator.write16(cpu.sp, addr) 284 | } 285 | 286 | fun pop(): Int { 287 | val addr = emulator.read16(cpu.sp) 288 | cpu.sp = cpu.sp + 2 289 | return addr 290 | } 291 | 292 | fun pushReg(reg16: Reg16) { 293 | cpu.sp = cpu.sp - 2 294 | emulator.write16(cpu.sp, cpu.readReg(reg16)) 295 | } 296 | 297 | fun popReg(reg16: Reg16) { 298 | cpu.writeReg(reg16, emulator.read16(cpu.sp)) 299 | cpu.sp = cpu.sp + 2 300 | } 301 | 302 | // Others 303 | 304 | fun swap(b: Byte): Byte { 305 | val value = b.toUnsignedInt() 306 | val result = ((value and 0x0F shl 4) or (value and 0xF0 shr 4)) 307 | if (result and 0xFF == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 308 | cpu.resetFlag(Flag.N) 309 | cpu.resetFlag(Flag.H) 310 | cpu.resetFlag(Flag.C) 311 | return result.toByte() 312 | } 313 | 314 | fun swapReg(reg: Reg) { 315 | cpu.writeReg(reg, swap(cpu.readReg(reg))) 316 | } 317 | 318 | fun daa() { 319 | var regA = cpu.readRegInt(Reg.A) 320 | 321 | if (cpu.isFlagSet(Flag.N) == false) { 322 | if (cpu.isFlagSet(Flag.H) || (regA and 0xF) > 9) regA += 0x06 323 | if (cpu.isFlagSet(Flag.C) || regA > 0x9F) regA += 0x60 324 | } else { 325 | if (cpu.isFlagSet(Flag.H)) regA = (regA - 6) and 0xFF 326 | if (cpu.isFlagSet(Flag.C)) regA -= 0x60 327 | } 328 | 329 | cpu.resetFlag(Flag.H) 330 | cpu.resetFlag(Flag.Z) 331 | if ((regA and 0x100) == 0x100) { 332 | cpu.setFlag(Flag.C) 333 | } 334 | 335 | regA = regA and 0xFF 336 | 337 | if (regA == 0) cpu.setFlag(Flag.Z) 338 | 339 | cpu.writeReg(Reg.A, regA.toByte()) 340 | } 341 | 342 | fun cpl() { 343 | cpu.writeReg(Reg.A, cpu.readRegInt(Reg.A).inv().toByte()) 344 | cpu.setFlag(Flag.N) 345 | cpu.setFlag(Flag.H) 346 | } 347 | 348 | fun ccf() { 349 | cpu.resetFlag(Flag.N) 350 | cpu.resetFlag(Flag.H) 351 | cpu.toggleFlag(Flag.C) 352 | } 353 | 354 | fun scf() { 355 | cpu.resetFlag(Flag.N) 356 | cpu.resetFlag(Flag.H) 357 | cpu.setFlag(Flag.C) 358 | } 359 | 360 | // Jumps 361 | 362 | fun jp(): Boolean { 363 | cpu.pc = emulator.read16(cpu.pc + 1) 364 | return true 365 | } 366 | 367 | fun jpHL(): Boolean { 368 | cpu.pc = cpu.readReg(Reg16.HL) 369 | return true 370 | } 371 | 372 | fun jpNZ(): Boolean { 373 | return invokeIfZFlagReset { jp() } 374 | } 375 | 376 | fun jpZ(): Boolean { 377 | return invokeIfZFlagSet { jp() } 378 | 379 | } 380 | 381 | fun jpNC(): Boolean { 382 | return invokeIfCFlagReset { jp() } 383 | 384 | } 385 | 386 | fun jpC(): Boolean { 387 | return invokeIfCFlagSet { jp() } 388 | } 389 | 390 | fun jr(): Boolean { 391 | cpu.pc = cpu.pc + emulator.read(cpu.pc + 1) + 2 //jr is 2 bytes long which actually needs to be acknowledged here 392 | return true 393 | } 394 | 395 | fun jrNZ(): Boolean { 396 | return invokeIfZFlagReset { jr() } 397 | } 398 | 399 | fun jrZ(): Boolean { 400 | return invokeIfZFlagSet { jr() } 401 | } 402 | 403 | fun jrNC(): Boolean { 404 | return invokeIfCFlagReset { jr() } 405 | } 406 | 407 | fun jrC(): Boolean { 408 | return invokeIfCFlagSet { jr() } 409 | } 410 | 411 | // Calls 412 | 413 | fun call(): Boolean { 414 | push(cpu.pc + 3) //each CALL instructions is 3 bytes long 415 | jp() 416 | return true 417 | } 418 | 419 | fun callNZ(): Boolean { 420 | return invokeIfZFlagReset { call() } 421 | } 422 | 423 | fun callZ(): Boolean { 424 | return invokeIfZFlagSet { call() } 425 | } 426 | 427 | fun callNC(): Boolean { 428 | return invokeIfCFlagReset { call() } 429 | } 430 | 431 | fun callC(): Boolean { 432 | return invokeIfCFlagSet { call() } 433 | } 434 | 435 | // Restarts 436 | 437 | fun rst(addr: Int): Boolean { 438 | push(cpu.pc + 1) //each RST is one byte long 439 | cpu.pc = addr 440 | return true 441 | } 442 | 443 | // Returns 444 | 445 | fun ret(): Boolean { 446 | cpu.pc = pop() 447 | return true 448 | } 449 | 450 | fun reti(): Boolean { 451 | cpu.pc = pop() 452 | cpu.setImeFlagNow(true) 453 | return true 454 | } 455 | 456 | fun retNZ(): Boolean { 457 | return invokeIfZFlagReset { ret() } 458 | } 459 | 460 | fun retZ(): Boolean { 461 | return invokeIfZFlagSet { ret() } 462 | } 463 | 464 | fun retNC(): Boolean { 465 | return invokeIfCFlagReset { ret() } 466 | } 467 | 468 | fun retC(): Boolean { 469 | return invokeIfCFlagSet { ret() } 470 | } 471 | 472 | // Rotates and shifts 473 | 474 | fun rlc(byte: Byte, sefZFlagIfNeeded: Boolean): Byte { 475 | val result = byte.rotateLeft(1) 476 | 477 | if (sefZFlagIfNeeded) { 478 | if (result.toUnsignedInt() == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 479 | } else { 480 | cpu.resetFlag(Flag.Z) 481 | } 482 | cpu.resetFlag(Flag.N) 483 | cpu.resetFlag(Flag.H) 484 | cpu.setFlagState(Flag.C, byte.isBitSet(7)) 485 | return result 486 | } 487 | 488 | fun rrc(byte: Byte, sefZFlagIfNeeded: Boolean): Byte { 489 | val result = byte.rotateRight(1) 490 | 491 | if (sefZFlagIfNeeded) { 492 | if (result.toUnsignedInt() == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 493 | } else { 494 | cpu.resetFlag(Flag.Z) 495 | } 496 | cpu.resetFlag(Flag.N) 497 | cpu.resetFlag(Flag.H) 498 | cpu.setFlagState(Flag.C, byte.isBitSet(0)) 499 | return result 500 | } 501 | 502 | fun rl(byte: Byte, sefZFlagIfNeeded: Boolean): Byte { 503 | var result = byte.rotateLeft(1) 504 | 505 | result = result.setBitState(0, cpu.isFlagSet(Flag.C)) 506 | 507 | if (sefZFlagIfNeeded) { 508 | if (result.toUnsignedInt() == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 509 | } else { 510 | cpu.resetFlag(Flag.Z) 511 | } 512 | cpu.resetFlag(Flag.N) 513 | cpu.resetFlag(Flag.H) 514 | cpu.setFlagState(Flag.C, byte.isBitSet(7)) 515 | return result.toByte() 516 | } 517 | 518 | fun rr(byte: Byte, sefZFlagIfNeeded: Boolean): Byte { 519 | var result = byte.rotateRight(1) 520 | 521 | result = result.setBitState(7, cpu.isFlagSet(Flag.C)) 522 | 523 | if (sefZFlagIfNeeded) { 524 | if (result.toUnsignedInt() == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 525 | } else { 526 | cpu.resetFlag(Flag.Z) 527 | } 528 | cpu.resetFlag(Flag.N) 529 | cpu.resetFlag(Flag.H) 530 | cpu.setFlagState(Flag.C, byte.isBitSet(0)) 531 | return result.toByte() 532 | } 533 | 534 | fun rlcReg(reg: Reg, sefZFlagIfNeeded: Boolean = true) { 535 | val value = cpu.readReg(reg) 536 | cpu.writeReg(reg, rlc(value, sefZFlagIfNeeded)) 537 | } 538 | 539 | fun rrcReg(reg: Reg, sefZFlagIfNeeded: Boolean = true) { 540 | val value = cpu.readReg(reg) 541 | cpu.writeReg(reg, rrc(value, sefZFlagIfNeeded)) 542 | } 543 | 544 | fun rrReg(reg: Reg, sefZFlagIfNeeded: Boolean = true) { 545 | val value = cpu.readReg(reg) 546 | cpu.writeReg(reg, rr(value, sefZFlagIfNeeded)) 547 | } 548 | 549 | fun rlReg(reg: Reg, sefZFlagIfNeeded: Boolean = true) { 550 | val value = cpu.readReg(reg) 551 | cpu.writeReg(reg, rl(value, sefZFlagIfNeeded)) 552 | } 553 | 554 | fun sla(byte: Byte): Byte { 555 | var result = (byte.toInt() and 0xFF) shl 1 556 | result = result and 0xFF 557 | 558 | if (result == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 559 | cpu.resetFlag(Flag.N) 560 | cpu.resetFlag(Flag.H) 561 | cpu.setFlagState(Flag.C, byte.isBitSet(7)) 562 | 563 | return result.toByte() 564 | } 565 | 566 | fun srl(byte: Byte): Byte { 567 | var result = (byte.toInt() and 0xFF) shr 1 568 | result = result and 0xFF 569 | 570 | if (result == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 571 | cpu.resetFlag(Flag.N) 572 | cpu.resetFlag(Flag.H) 573 | cpu.setFlagState(Flag.C, byte.isBitSet(0)) 574 | 575 | return result.toByte() 576 | } 577 | 578 | fun sra(byte: Byte): Byte { 579 | var result = (byte.toInt() and 0xFF) ushr 1 580 | result = result and 0xFF 581 | result = result.toByte().setBitState(7, byte.isBitSet(7)).toUnsignedInt() //preserve msb 582 | 583 | if (result == 0) cpu.setFlag(Flag.Z) else cpu.resetFlag(Flag.Z) 584 | cpu.resetFlag(Flag.N) 585 | cpu.resetFlag(Flag.H) 586 | cpu.setFlagState(Flag.C, byte.isBitSet(0)) 587 | 588 | return result.toByte() 589 | } 590 | 591 | fun slaReg(reg: Reg) { 592 | val value = cpu.readReg(reg) 593 | cpu.writeReg(reg, sla(value)) 594 | } 595 | 596 | fun srlReg(reg: Reg) { 597 | val value = cpu.readReg(reg) 598 | cpu.writeReg(reg, srl(value)) 599 | } 600 | 601 | fun sraReg(reg: Reg) { 602 | val value = cpu.readReg(reg) 603 | cpu.writeReg(reg, sra(value)) 604 | } 605 | 606 | // Bit operations 607 | 608 | fun bit(bit: Int, byte: Byte) { 609 | cpu.setFlagState(Flag.Z, byte.isBitSet(bit) == false) 610 | cpu.resetFlag(Flag.N) 611 | cpu.setFlag(Flag.H) 612 | } 613 | 614 | fun set(bit: Int, byte: Byte): Byte { 615 | return byte.setBit(bit) 616 | } 617 | 618 | fun res(bit: Int, byte: Byte): Byte { 619 | return byte.resetBit(bit) 620 | } 621 | 622 | fun bitReg(bit: Int, reg: Reg) { 623 | bit(bit, cpu.readReg(reg)) 624 | } 625 | 626 | fun bitHL(bit: Int) { 627 | val addr = cpu.readReg(Reg16.HL) 628 | syncTimer(4) 629 | bit(bit, emulator.read(addr)) 630 | } 631 | 632 | fun setReg(bit: Int, reg: Reg) { 633 | val value = cpu.readReg(reg) 634 | cpu.writeReg(reg, set(bit, value)) 635 | } 636 | 637 | fun resReg(bit: Int, reg: Reg) { 638 | val value = cpu.readReg(reg) 639 | cpu.writeReg(reg, res(bit, value)) 640 | } 641 | 642 | // Misc 643 | 644 | fun syncTimer(cycles: Int) { 645 | emulator.io.sync(cycles) 646 | } 647 | 648 | fun syncedHLRegOp(transform: (Byte) -> Byte) { 649 | syncTimer(4) 650 | val addr = cpu.readReg(Reg16.HL) 651 | val value = emulator.read(addr) 652 | syncTimer(4) 653 | emulator.write(addr, transform(value)) 654 | } 655 | 656 | // Internal util 657 | 658 | private fun invokeIfCFlagSet(runnable: () -> Boolean): Boolean { 659 | if (cpu.isFlagSet(Flag.C)) { 660 | return runnable.invoke() 661 | } else { 662 | return false 663 | } 664 | } 665 | 666 | private fun invokeIfCFlagReset(runnable: () -> Boolean): Boolean { 667 | if (cpu.isFlagSet(Flag.C) == false) { 668 | return runnable.invoke() 669 | } else { 670 | return false 671 | } 672 | } 673 | 674 | private fun invokeIfZFlagSet(runnable: () -> Boolean): Boolean { 675 | if (cpu.isFlagSet(Flag.Z)) { 676 | return runnable.invoke() 677 | } else { 678 | return false 679 | } 680 | } 681 | 682 | private fun invokeIfZFlagReset(runnable: () -> Boolean): Boolean { 683 | if (cpu.isFlagSet(Flag.Z) == false) { 684 | return runnable.invoke() 685 | } else { 686 | return false 687 | } 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/gdx/GdxGpu.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.gdx 2 | 3 | import com.badlogic.gdx.graphics.Color 4 | import com.badlogic.gdx.graphics.Pixmap 5 | import com.kotcrab.xgbc.io.Gpu 6 | 7 | /** @author Kotcrab */ 8 | class GdxGpu(private val gpu: Gpu) { 9 | private val tmpTileBuffer = IntArray(64) 10 | 11 | val color0 = Color(224f / 255f, 248f / 255f, 208f / 255f, 1f) 12 | val color1 = Color(136f / 255f, 192f / 255f, 112f / 255f, 1f) 13 | val color2 = Color(52f / 255f, 104f / 255f, 86f / 255f, 1f) 14 | val color3 = Color(8f / 255f, 24f / 255f, 32f / 255f, 1f) 15 | val colors = arrayOf(Color.rgba8888(color0), Color.rgba8888(color1), Color.rgba8888(color2), Color.rgba8888(color3)) 16 | 17 | fun drawPatternTileToPixmap(pixmap: Pixmap, xOffset: Int, yOffset: Int, patternTableAddr: Int, tileId: Int) { 18 | val buffer = gpu.readTilePatternTable(patternTableAddr, tileId, tmpTileBuffer) 19 | 20 | for (i in 0..7) { 21 | for (ii in 0..7) { 22 | pixmap.drawPixel(xOffset + (7 - ii), yOffset + i, colors[buffer[i * 8 + ii]]) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/gdx/GdxJoypad.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.gdx 2 | 3 | import com.badlogic.gdx.Input 4 | import com.badlogic.gdx.InputAdapter 5 | import com.kotcrab.xgbc.io.Joypad 6 | 7 | /** @author Kotcrab */ 8 | class GdxJoypad(private val joypad: Joypad) : InputAdapter() { 9 | val mapping = mapOf( 10 | Input.Keys.X to Joypad.JoypadKey.A, 11 | Input.Keys.Z to Joypad.JoypadKey.B, 12 | Input.Keys.UP to Joypad.JoypadKey.UP, 13 | Input.Keys.LEFT to Joypad.JoypadKey.LEFT, 14 | Input.Keys.DOWN to Joypad.JoypadKey.DOWN, 15 | Input.Keys.RIGHT to Joypad.JoypadKey.RIGHT, 16 | Input.Keys.ENTER to Joypad.JoypadKey.START, 17 | Input.Keys.BACKSPACE to Joypad.JoypadKey.SELECT 18 | ) 19 | 20 | override fun keyDown(keycode: Int): Boolean { 21 | val key = mapping[keycode] 22 | if (key != null) { 23 | joypad.keyPressed(key) 24 | return true 25 | } 26 | return false 27 | } 28 | 29 | override fun keyUp(keycode: Int): Boolean { 30 | val key = mapping[keycode] 31 | if (key != null) { 32 | joypad.keyReleased(key) 33 | return true 34 | } 35 | return false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/io/Div.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.io 2 | 3 | import com.kotcrab.xgbc.Emulator 4 | 5 | /** @author Kotcrab */ 6 | class Div(private val emulator: Emulator) : IODevice { 7 | val DIV = 0xFF04 8 | 9 | val cycleUpdate = 256 //16384 hz 10 | var cycleCounter = 0 11 | 12 | override fun register(registrar: (Int) -> Unit) { 13 | registrar(DIV) 14 | } 15 | 16 | override fun tick(cyclesElapsed: Int) { 17 | cycleCounter += cyclesElapsed 18 | var div = emulator.readInt(DIV) 19 | while (cycleCounter >= cycleUpdate) { 20 | cycleCounter -= cycleUpdate 21 | div += 1 22 | if (div > 0xFF) { 23 | div = 0 24 | } 25 | } 26 | emulator.io.directWrite(DIV, div.toByte()) 27 | } 28 | 29 | override fun reset() { 30 | cycleCounter = 0 31 | } 32 | 33 | override fun onRead(addr: Int) { 34 | } 35 | 36 | override fun onWrite(addr: Int, value: Byte) { 37 | emulator.io.directWrite(DIV, 0) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/io/Gpu.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.io 2 | 3 | import com.kotcrab.xgbc.Emulator 4 | import com.kotcrab.xgbc.toInt 5 | 6 | /** @author Kotcrab */ 7 | class Gpu(private val emulator: Emulator) { 8 | companion object { 9 | val TILE_SIZE = 8 10 | val TILE_BYTE_SIZE = 16 11 | val PATTERN_TABLE_0 = 0x8800 12 | val PATTERN_TABLE_1 = 0x8000 13 | 14 | val TIME_MAP_DATA_SIZE = 32 * 32 //32 rows, 32 bytes each 15 | val TILE_MAP_DATA_0 = 0x9800 16 | val TILE_MAP_DATA_1 = 0x9C00 17 | } 18 | 19 | val vram: ByteArray = ByteArray(0x2000) 20 | 21 | fun reset() { 22 | vram.fill(0) 23 | } 24 | 25 | fun readTilePatternTable(patternTableAddr: Int, tileId: Int, buffer: IntArray): IntArray { 26 | val tileStart = patternTableAddr + (tileId * TILE_BYTE_SIZE) 27 | return readTile(tileStart, buffer) 28 | } 29 | 30 | fun readTile(tileStartAddr: Int, buffer: IntArray): IntArray { 31 | if (buffer.size < 64) throw IllegalStateException("buffer must be at least 64 bytes") 32 | 33 | var bufferIdx = 0 34 | var lineIdx = 0 35 | 36 | for (i in tileStartAddr..tileStartAddr + TILE_BYTE_SIZE - 1 step 2) { 37 | val byte = emulator.readInt(i) 38 | val byte2 = emulator.readInt(i + 1) 39 | 40 | for (ii in 0..7) { 41 | val colorLSB = byte and (1 shl ii) != 0 42 | val colorMSB = byte2 and (1 shl ii) != 0 43 | val colorVal = colorLSB.toInt() or (colorMSB.toInt() shl 1) 44 | buffer[bufferIdx++] = colorVal 45 | } 46 | lineIdx++ 47 | } 48 | 49 | return buffer 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/io/IO.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.io 2 | 3 | import com.kotcrab.xgbc.Emulator 4 | 5 | /** @author Kotcrab */ 6 | class IO(private val emulator: Emulator) { 7 | val ioMem: ByteArray = ByteArray(0x4C) 8 | val devicesMap = arrayOfNulls(0x4C) 9 | 10 | val serialPort = SerialPort(emulator) 11 | val div = Div(emulator) 12 | val timer = Timer(emulator) 13 | val lcd = Lcd(emulator) 14 | val joypad = Joypad(emulator) 15 | 16 | val devices = arrayOf(serialPort, timer, div, lcd, joypad) 17 | 18 | var cyclesSynced = 0 19 | 20 | init { 21 | for (device in devices) { 22 | device.register({ addr -> devicesMap[addr - 0xFF00] = device }) 23 | } 24 | } 25 | 26 | fun reset() { 27 | ioMem.fill(0) 28 | for (device in devices) { 29 | device.reset() 30 | } 31 | cyclesSynced = 0 32 | } 33 | 34 | fun tick(cyclesElapsed: Int) { 35 | for (device in devices) { 36 | device.tick(cyclesElapsed) 37 | } 38 | } 39 | 40 | fun sync(cycles: Int) { 41 | cyclesSynced += cycles 42 | tick(cycles) 43 | } 44 | 45 | fun getAndClearSyncedCycles(): Int { 46 | val cyclesTmp = cyclesSynced 47 | cyclesSynced = 0 48 | return cyclesTmp 49 | } 50 | 51 | fun read(addr: Int): Byte { 52 | val relAddr = addr - 0xFF00 53 | val device = devicesMap[relAddr] 54 | device?.onRead(addr) 55 | return ioMem[relAddr] 56 | } 57 | 58 | fun write(addr: Int, value: Byte) { 59 | val relAddr = addr - 0xFF00 60 | ioMem[relAddr] = value 61 | val device = devicesMap[relAddr] 62 | device?.onWrite(addr, value) 63 | } 64 | 65 | fun directWrite(addr: Int, value: Byte) { 66 | ioMem[addr - 0xFF00] = value 67 | } 68 | } 69 | 70 | interface IODevice { 71 | fun reset() 72 | 73 | fun tick(cyclesElapsed: Int) 74 | 75 | fun register(registrar: (addr: Int) -> Unit) 76 | 77 | fun onRead(addr: Int) 78 | 79 | fun onWrite(addr: Int, value: Byte) 80 | } 81 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/io/Joypad.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.io 2 | 3 | import com.kotcrab.xgbc.* 4 | import java.util.* 5 | 6 | /** @author Kotcrab */ 7 | class Joypad(private val emulator: Emulator) : IODevice { 8 | val P1 = 0xFF00 9 | 10 | val pressedKeys = EnumSet.noneOf(JoypadKey::class.java)!! 11 | 12 | override fun register(registrar: (Int) -> Unit) { 13 | registrar(P1) 14 | } 15 | 16 | override fun tick(cyclesElapsed: Int) { 17 | } 18 | 19 | override fun reset() { 20 | pressedKeys.clear() 21 | } 22 | 23 | override fun onRead(addr: Int) { 24 | } 25 | 26 | override fun onWrite(addr: Int, value: Byte) { 27 | if (addr == P1) updateMemoryData() 28 | } 29 | 30 | private fun updateMemoryData() { 31 | val p1Value = emulator.read(P1) 32 | var newValue = (p1Value.toUnsignedInt() and 0b00110000 or 0b11001111).toByte() 33 | for (key in pressedKeys) { 34 | if (newValue.isBitSet(key.selectBit) == false) { 35 | newValue = newValue.resetBit(key.inBit) 36 | } 37 | } 38 | emulator.io.directWrite(P1, newValue) 39 | } 40 | 41 | fun keyPressed(key: JoypadKey) { 42 | pressedKeys.add(key) 43 | updateMemoryData() 44 | if (emulator.read(P1).isBitSet(key.selectBit)) { 45 | emulator.interrupt(Interrupt.JOYPAD) 46 | } 47 | } 48 | 49 | fun keyReleased(key: JoypadKey) { 50 | val removed = pressedKeys.remove(key) 51 | if (removed == false) return 52 | updateMemoryData() 53 | } 54 | 55 | enum class JoypadKey(val selectBit: Int, val inBit: Int) { 56 | LEFT(4, 1), RIGHT(4, 0), UP(4, 2), DOWN(4, 3), 57 | A(5, 0), B(5, 1), START(5, 3), SELECT(5, 2) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/io/Lcd.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.io 2 | 3 | import com.kotcrab.xgbc.* 4 | 5 | /** @author Kotcrab */ 6 | class Lcd(private val emulator: Emulator) : IODevice { 7 | val gpu = emulator.gpu 8 | 9 | val LCDC = 0xFF40 10 | val STAT = 0xFF41 11 | val SCY = 0xFF42 12 | val SCX = 0xFF43 13 | val LY = 0xFF44 14 | val LYC = 0xFF45 //TODO compare with LY 15 | val DMA = 0xFF46 16 | val BGP = 0xFF47 17 | val OBP0 = 0xFF48 18 | val OBP1 = 0xFF49 19 | val WY = 0xFF4A 20 | val WX = 0xFF4B 21 | 22 | //val cycleVBlank = 70224 //59.7 hz 23 | 24 | var scanLine = 0 25 | var mode = Mode.OAM_SEARCH 26 | var cycleCounter = 0 27 | 28 | override fun register(registrar: (Int) -> Unit) { 29 | registrar(LCDC) 30 | registrar(STAT) 31 | registrar(SCY) 32 | registrar(SCX) 33 | registrar(LY) 34 | registrar(LYC) 35 | registrar(DMA) 36 | registrar(BGP) 37 | registrar(OBP0) 38 | registrar(OBP1) 39 | registrar(WY) 40 | registrar(WX) 41 | } 42 | 43 | override fun reset() { 44 | } 45 | 46 | override fun tick(cyclesElapsed: Int) { 47 | cycleCounter += cyclesElapsed 48 | while (cycleCounter >= mode.cycles) { 49 | cycleCounter -= mode.cycles 50 | 51 | when (mode) { 52 | Mode.OAM_SEARCH -> { 53 | mode = Mode.LCD_TRANSFER 54 | emulator.lcdTransferHandler.invoke() 55 | scanLine++ 56 | } 57 | Mode.LCD_TRANSFER -> { 58 | mode = Mode.HBLANK 59 | } 60 | Mode.HBLANK -> { 61 | if (scanLine == 144) { 62 | mode = Mode.VBLANK 63 | } else { 64 | mode = Mode.OAM_SEARCH 65 | } 66 | emulator.io.directWrite(LY, scanLine.toByte()) 67 | } 68 | Mode.VBLANK -> { 69 | if (scanLine == 144) { 70 | emulator.interrupt(Interrupt.VBLANK) 71 | } 72 | 73 | scanLine++ 74 | if (scanLine == 154) { 75 | mode = Mode.OAM_SEARCH 76 | scanLine = 0 77 | } 78 | emulator.io.directWrite(LY, scanLine.toByte()) 79 | } 80 | } 81 | 82 | var stat = emulator.read(STAT).setBitState(2, emulator.read(LYC) == emulator.read(LY)).toUnsignedInt() 83 | stat = stat and 0b11111100 84 | when (mode) { 85 | Mode.OAM_SEARCH -> { 86 | stat = stat or 0b10 87 | if (stat.toByte().isBitSet(5)) emulator.interrupt(Interrupt.LCDC) 88 | } 89 | Mode.LCD_TRANSFER -> { 90 | stat = stat or 0b11 91 | } 92 | Mode.HBLANK -> { 93 | stat = stat or 0b00 94 | if (stat.toByte().isBitSet(3)) emulator.interrupt(Interrupt.LCDC) 95 | } 96 | Mode.VBLANK -> { 97 | stat = stat or 0b01 98 | if (stat.toByte().isBitSet(4)) emulator.interrupt(Interrupt.LCDC) 99 | } 100 | } 101 | val statByte = stat.toByte() 102 | //if (statByte.isBitSet(6) && statByte.isBitSet(2)) emulator.interrupt(Interrupt.LCDC) 103 | emulator.io.directWrite(STAT, statByte) 104 | } 105 | } 106 | 107 | override fun onRead(addr: Int) { 108 | 109 | } 110 | 111 | override fun onWrite(addr: Int, value: Byte) { 112 | if (addr == LY) { 113 | emulator.io.directWrite(LY, 0) 114 | } 115 | } 116 | 117 | fun isLcdEnabled(): Boolean { 118 | return emulator.read(LCDC).isBitSet(7) == true 119 | } 120 | 121 | fun getWindowTileMapDataAddr(): Int { 122 | return if (emulator.read(LCDC).isBitSet(6)) Gpu.TILE_MAP_DATA_1 else Gpu.TILE_MAP_DATA_0 123 | } 124 | 125 | fun isWindowDisplayEnabled(): Boolean { 126 | return emulator.read(LCDC).isBitSet(5) == true 127 | } 128 | 129 | fun getPatternDataAddr(): Int { 130 | return if (emulator.read(LCDC).isBitSet(4)) Gpu.PATTERN_TABLE_1 else Gpu.PATTERN_TABLE_0 131 | } 132 | 133 | fun getBgTileMapDataAddr(): Int { 134 | return if (emulator.read(LCDC).isBitSet(3)) Gpu.TILE_MAP_DATA_1 else Gpu.TILE_MAP_DATA_0 135 | } 136 | 137 | fun isSpriteDisplayEnabled(): Boolean { 138 | return emulator.read(LCDC).isBitSet(1) == true 139 | } 140 | 141 | fun isBgAndWindowDisplayEnabled(): Boolean { 142 | return emulator.read(LCDC).isBitSet(0) == true 143 | } 144 | 145 | enum class Mode(val mode: Byte, val cycles: Int) { 146 | HBLANK(0, 204), 147 | OAM_SEARCH(2, 80), 148 | LCD_TRANSFER(3, 172), 149 | VBLANK(1, 456); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/io/SerialPort.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.io 2 | 3 | import com.kotcrab.xgbc.Emulator 4 | import com.kotcrab.xgbc.Interrupt 5 | 6 | /** @author Kotcrab */ 7 | class SerialPort(private val emulator: Emulator) : IODevice { 8 | val SB = 0xFF01 9 | val SC = 0xFF02 10 | 11 | var cycleCounter = 0 12 | val cycleUpdate = 512 // 8192 khz 13 | 14 | override fun register(registrar: (Int) -> Unit) { 15 | registrar(SB) 16 | registrar(SC) 17 | } 18 | 19 | override fun tick(cyclesElapsed: Int) { 20 | cycleCounter += cyclesElapsed 21 | if (cycleCounter >= cycleUpdate) { 22 | cycleCounter -= cycleUpdate 23 | val sc = emulator.readInt(SC) 24 | if (sc == 0x81) { 25 | print(emulator.read(SB).toChar()) 26 | emulator.write(SB, 0xFF) 27 | emulator.write(SC, 0x01) 28 | emulator.interrupt(Interrupt.SERIAL) 29 | } 30 | } 31 | } 32 | 33 | override fun reset() { 34 | cycleCounter = 0 35 | } 36 | 37 | override fun onRead(addr: Int) { 38 | } 39 | 40 | override fun onWrite(addr: Int, value: Byte) { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/io/Timer.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.io 2 | 3 | import com.kotcrab.xgbc.Emulator 4 | import com.kotcrab.xgbc.Interrupt 5 | import com.kotcrab.xgbc.isBitSet 6 | import com.kotcrab.xgbc.toUnsignedInt 7 | 8 | /** @author Kotcrab */ 9 | class Timer(private val emulator: Emulator) : IODevice { 10 | val TIMA = 0xFF05 11 | val TMA = 0xFF06 12 | val TAC = 0xFF07 13 | 14 | val clock00 = 1024 //4096 hz 15 | val clock01 = 16 //262144 hz 16 | val clock10 = 64 //65536 hz 17 | val clock11 = 256 //16384 hz 18 | 19 | var cycleUpdate = clock00 20 | var cycleCounter = 0 21 | 22 | override fun register(registrar: (Int) -> Unit) { 23 | registrar(TIMA) 24 | registrar(TMA) 25 | registrar(TAC) 26 | } 27 | 28 | override fun tick(cyclesElapsed: Int) { 29 | if (emulator.read(TAC).isBitSet(2)) { 30 | cycleCounter += cyclesElapsed 31 | var tima = emulator.readInt(TIMA) 32 | while (cycleCounter >= cycleUpdate) { 33 | //println("TAC:" + toHex(emulator.read(TAC)) + " TIMA:" + toHex(emulator.read(TIMA)) + " TMA:" + toHex(emulator.read(TMA))) 34 | cycleCounter -= cycleUpdate 35 | tima++ 36 | 37 | if (tima > 0xFF) { 38 | tima = emulator.readInt(TMA) 39 | emulator.interrupt(Interrupt.TIMER) 40 | } 41 | } 42 | 43 | emulator.io.directWrite(TIMA, tima.toByte()) 44 | } 45 | } 46 | 47 | override fun reset() { 48 | cycleUpdate = clock00 49 | cycleCounter = 0 50 | } 51 | 52 | override fun onRead(addr: Int) { 53 | } 54 | 55 | override fun onWrite(addr: Int, value: Byte) { 56 | //println("TAC:" + toHex(emulator.read(TAC)) + " TIMA:" + toHex(emulator.read(TIMA)) + " TMA:" + toHex(emulator.read(TMA))) 57 | if (addr != TAC) return 58 | val timerClk = value.toUnsignedInt() and 0b11 59 | when (timerClk) { 60 | 0b00 -> cycleUpdate = clock00 61 | 0b01 -> cycleUpdate = clock01 62 | 0b10 -> cycleUpdate = clock10 63 | 0b11 -> cycleUpdate = clock11 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/rom/Rom.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.rom 2 | 3 | import com.badlogic.gdx.files.FileHandle 4 | import com.kotcrab.xgbc.EmulatorException 5 | import com.kotcrab.xgbc.rom.mbc.MBC 6 | import com.kotcrab.xgbc.rom.mbc.MBC1 7 | import com.kotcrab.xgbc.rom.mbc.RomOnly 8 | import com.kotcrab.xgbc.toUnsignedInt 9 | 10 | /** @author Kotcrab */ 11 | class Rom(romFile: FileHandle) { 12 | val rom: ByteArray = romFile.readBytes() 13 | lateinit var mbc: MBC 14 | 15 | val title: String by lazy { 16 | val builder = StringBuilder() 17 | for (addr in 0x0134..0x0142) { 18 | val byte = read(addr) 19 | if (byte.equals(0)) break 20 | builder.append(byte.toChar()) 21 | } 22 | builder.toString() 23 | } 24 | 25 | val gameBoyColor: Boolean by lazy { 26 | read(0x143).equals(0x80) 27 | } 28 | 29 | val superGameBoy: Boolean by lazy { 30 | read(0x146).equals(0x03) 31 | } 32 | 33 | val cartridgeType: CartridgeType by lazy { 34 | cartridgeTypeFromByte(read(0x147)) 35 | } 36 | 37 | val romSize: Int by lazy { 38 | val size: Int 39 | when (readInt(0x148)) { 40 | 0x0 -> size = 32 * 1024 41 | 0x1 -> size = 64 * 1024 42 | 0x2 -> size = 128 * 1024 43 | 0x3 -> size = 256 * 1024 44 | 0x4 -> size = 512 * 1024 45 | 0x5 -> size = 1024 * 1024 46 | 0x6 -> size = 2048 * 1024 47 | 0x52 -> size = 72 * 16 * 1024 //72 banks 48 | 0x53 -> size = 80 * 16 * 1024 49 | 0x54 -> size = 96 * 16 * 1024 50 | else -> throw EmulatorException("Unknown ROM size") 51 | } 52 | size 53 | } 54 | 55 | val ramSize: Int by lazy { 56 | val size: Int 57 | when (readInt(0x149)) { 58 | 0x0 -> size = 0 59 | 0x1 -> size = 2 * 1024 60 | 0x2 -> size = 8 * 1024 61 | 0x3 -> size = 32 * 1024 62 | 0x4 -> size = 128 * 1024 63 | else -> throw EmulatorException("Unknown RAM size") 64 | } 65 | size 66 | } 67 | 68 | val destCode: Int by lazy { 69 | readInt(0x014A) 70 | } 71 | 72 | init { 73 | when (cartridgeType) { 74 | CartridgeType.ROM -> mbc = RomOnly(this) 75 | CartridgeType.ROM_MBC1 -> mbc = MBC1(this) 76 | CartridgeType.ROM_MBC1_RAM -> mbc = MBC1(this) 77 | CartridgeType.ROM_MBC1_RAM_BATT -> mbc = MBC1(this) 78 | else -> throw EmulatorException("Unsupported memory bank controller: $cartridgeType") 79 | } 80 | } 81 | 82 | fun read(addr: Int): Byte { 83 | return rom[addr] 84 | } 85 | 86 | private fun readInt(addr: Int): Int { 87 | return read(addr).toUnsignedInt() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/rom/RomEnums.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.rom 2 | 3 | import com.kotcrab.xgbc.EmulatorException 4 | 5 | /** @author Kotcrab */ 6 | enum class CartridgeType(val type: Byte) { 7 | ROM(0x0), 8 | ROM_MBC1(0x1), 9 | ROM_MBC1_RAM(0x2), 10 | ROM_MBC1_RAM_BATT(0x3), 11 | ROM_MBC2(0x5), 12 | ROM_MBC2_BATT(0x6), 13 | ROM_RAM(0x8), 14 | ROM_RAM_BATT(0x9), 15 | ROM_MMMO1(0xB), 16 | ROM_MMMO1_SRAM(0xC), 17 | ROM_MMMO1_SRAM_BATT(0xD), 18 | ROM_MBC3_TIMER_BATT(0xF), 19 | ROM_MBC3_TIMER_RAM_BATT(0x10), 20 | ROM_MBC3(0x11), 21 | ROM_MBC3_RAM(0x12), 22 | ROM_MBC3_RAM_BATT(0x13), 23 | ROM_MBC5(0x19), 24 | ROM_MBC5_RAM(0x1A), 25 | ROM_MBC5_RAM_BATT(0x1B), 26 | ROM_MBC5_RUMBLE(0x1C), 27 | ROM_MBC5_RUMBLE_SRAM(0x1D), 28 | ROM_MBC5_RUMBLE_SRAM_BATT(0x1E), 29 | POCKET_CAMERA(0x1F), 30 | BANDAI_TAMA5(0xFD.toByte()), 31 | HUDSON_HUC3(0xFE.toByte()), 32 | HUDSON_HUC1(0xFF.toByte()) 33 | } 34 | 35 | fun cartridgeTypeFromByte(type: Byte): CartridgeType { 36 | for (enumType in CartridgeType.values()) { 37 | if (enumType.type == type) return enumType 38 | } 39 | 40 | throw EmulatorException("Unsupported cartridge type: " + type) 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/rom/mbc/MBC.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.rom.mbc 2 | 3 | /** @author Kotcrab */ 4 | interface MBC { 5 | fun read(addr: Int): Byte 6 | 7 | fun write(addr: Int, value: Byte) 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/rom/mbc/MBC1.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.rom.mbc 2 | 3 | import com.kotcrab.xgbc.EmulatorException 4 | import com.kotcrab.xgbc.isBitSet 5 | import com.kotcrab.xgbc.rom.Rom 6 | import com.kotcrab.xgbc.toHex 7 | import com.kotcrab.xgbc.toUnsignedInt 8 | 9 | /** @author Kotcrab */ 10 | class MBC1(private val rom: Rom) : MBC { 11 | var mode = MBC1Mode.ROM16_RAM8 12 | 13 | var romBankSelector = 0 14 | var activeRomBank = 1 15 | 16 | var ramEnabled = false 17 | val ramBanks: Array = Array(4, { i -> ByteArray(0x2000) }) 18 | var activeRamBank = ramBanks[0] 19 | 20 | override fun write(addr: Int, value: Byte) { 21 | if (addr in 0x0000..0x2000 - 1) { 22 | val ramWrite = value.toUnsignedInt() and 0xF 23 | ramEnabled = (ramWrite == 0x0A) 24 | return 25 | } 26 | 27 | if (addr in 0x2000..0x4000) { 28 | romBankSelector = romBankSelector and 0xE0 29 | romBankSelector = romBankSelector or (value.toUnsignedInt() and 0x1F) 30 | updateRomBank() 31 | return 32 | } 33 | 34 | if (addr in 0x4000..0x6000 - 1) { 35 | if (mode == MBC1Mode.ROM16_RAM8) { 36 | romBankSelector = romBankSelector and 0xCF 37 | romBankSelector = romBankSelector or (value.toUnsignedInt() shl 4) 38 | updateRomBank() 39 | } else { 40 | activeRamBank = ramBanks[value.toUnsignedInt() and 0x03] 41 | } 42 | } 43 | 44 | if (addr in 0x6000..0x8000 - 1) { 45 | if (value.isBitSet(0)) { 46 | mode = MBC1Mode.ROM16_RAM8 47 | } else { 48 | mode = MBC1Mode.ROM4_RAM32 49 | } 50 | return 51 | } 52 | 53 | if (addr in 0xA000..0xC000 - 1) { 54 | if (ramEnabled) { 55 | activeRamBank[addr - 0xA000] = value 56 | } 57 | return 58 | } 59 | 60 | throw EmulatorException("Unsupported MBC1 write: at ${toHex(addr)}, value: ${toHex(value)}") 61 | } 62 | 63 | private fun updateRomBank() { 64 | activeRomBank = romBankSelector 65 | if (activeRomBank == 0x00) activeRomBank = 0x01 66 | if (activeRomBank == 0x20) activeRomBank = 0x21 67 | if (activeRomBank == 0x40) activeRomBank = 0x41 68 | if (activeRomBank == 0x60) activeRomBank = 0x61 69 | } 70 | 71 | override fun read(addr: Int): Byte { 72 | if (addr in 0x0000..0x4000 - 1) return rom.read(addr) 73 | if (addr in 0x4000..0x8000 - 1) return rom.read((activeRomBank - 1) * 0x4000 + addr) 74 | 75 | if (addr in 0xA000..0xC000 - 1) { 76 | if (ramEnabled) { 77 | return activeRamBank[addr - 0xA000] 78 | } else { 79 | return 0 80 | } 81 | } 82 | 83 | throw EmulatorException("Read address out of supported MBC1 read range: ${toHex(addr)}") 84 | } 85 | 86 | enum class MBC1Mode { 87 | ROM16_RAM8, 88 | ROM4_RAM32 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/rom/mbc/RomOnly.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.rom.mbc 2 | 3 | import com.kotcrab.xgbc.rom.Rom 4 | 5 | /** @author Kotcrab */ 6 | class RomOnly(private val rom: Rom) : MBC { 7 | override fun write(addr: Int, value: Byte) { 8 | //throw EmulatorException("Illegal ROM write. This cartridge types does not use MBC") 9 | } 10 | 11 | override fun read(addr: Int): Byte { 12 | if (addr in 0xA000..0xC000 - 1) return 0 13 | 14 | return rom.read(addr) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/CpuDebuggerTable.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.ui 2 | 3 | import com.badlogic.gdx.graphics.g2d.Batch 4 | import com.kotcrab.vis.ui.widget.VisCheckBox 5 | import com.kotcrab.vis.ui.widget.VisLabel 6 | import com.kotcrab.vis.ui.widget.VisTable 7 | import com.kotcrab.xgbc.Emulator 8 | import com.kotcrab.xgbc.cpu.Flag 9 | import com.kotcrab.xgbc.cpu.Reg16 10 | import com.kotcrab.xgbc.toHex 11 | import com.kotcrab.xgbc.ui.TableBuilder 12 | 13 | /** @author Kotcrab */ 14 | class CpuDebuggerTable(private val emulator: Emulator) : VisTable(true) { 15 | private val afLabel = VisLabel() 16 | private val bcLabel = VisLabel() 17 | private val deLabel = VisLabel() 18 | private val hlLabel = VisLabel() 19 | 20 | private val pcLabel = VisLabel() 21 | private val spLabel = VisLabel() 22 | 23 | private val zFlagCheck = VisCheckBox("Z") 24 | private val nFlagCheck = VisCheckBox("N") 25 | private val hFlagCheck = VisCheckBox("H") 26 | private val cFlagCheck = VisCheckBox("C") 27 | 28 | init { 29 | left().top() 30 | defaults().left() 31 | 32 | val regsTable = VisTable(false) 33 | regsTable.add("AF:") 34 | regsTable.add(afLabel) 35 | 36 | regsTable.row() 37 | regsTable.add("BC:") 38 | regsTable.add(bcLabel) 39 | 40 | regsTable.row() 41 | regsTable.add("DE:") 42 | regsTable.add(deLabel) 43 | 44 | regsTable.row() 45 | regsTable.add("HL:") 46 | regsTable.add(hlLabel) 47 | 48 | val miscTable = VisTable(false) 49 | miscTable.defaults().left() 50 | 51 | miscTable.add(TableBuilder.build(VisLabel("PC:"), pcLabel)).row() 52 | miscTable.add(TableBuilder.build(VisLabel("SP:"), spLabel)).row() 53 | miscTable.add(TableBuilder.build(zFlagCheck, nFlagCheck)).row() 54 | miscTable.add(TableBuilder.build(cFlagCheck, hFlagCheck)).row() 55 | 56 | add("CPU:").row() 57 | add(regsTable).width(60.0f) 58 | add(miscTable) 59 | } 60 | 61 | override fun draw(batch: Batch?, parentAlpha: Float) { 62 | super.draw(batch, parentAlpha) 63 | 64 | afLabel.setText(toHex(emulator.cpu.readReg(Reg16.AF))) 65 | bcLabel.setText(toHex(emulator.cpu.readReg(Reg16.BC))) 66 | deLabel.setText(toHex(emulator.cpu.readReg(Reg16.DE))) 67 | hlLabel.setText(toHex(emulator.cpu.readReg(Reg16.HL))) 68 | 69 | pcLabel.setText(toHex(emulator.cpu.pc)) 70 | spLabel.setText(toHex(emulator.cpu.sp)) 71 | 72 | zFlagCheck.isChecked = emulator.cpu.isFlagSet(Flag.Z) 73 | nFlagCheck.isChecked = emulator.cpu.isFlagSet(Flag.N) 74 | hFlagCheck.isChecked = emulator.cpu.isFlagSet(Flag.H) 75 | cFlagCheck.isChecked = emulator.cpu.isFlagSet(Flag.C) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/DebuggerPopupMenu.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.ui 2 | 3 | import com.kotcrab.vis.ui.widget.MenuItem 4 | import com.kotcrab.vis.ui.widget.PopupMenu 5 | import com.kotcrab.xgbc.changed 6 | 7 | /** @author Kotcrab */ 8 | class DebuggerPopupMenu(val listener: Listener) : PopupMenu() { 9 | var ctxAddr = 0x0000 10 | 11 | init { 12 | val runToHere = MenuItem("Run to here") 13 | runToHere.changed { changeEvent, actor -> listener.runToLine(ctxAddr) } 14 | addItem(runToHere) 15 | } 16 | 17 | interface Listener { 18 | fun runToLine(ctxAddr: Int) 19 | 20 | fun addBreakpoint(addr: Int) 21 | 22 | fun removeBreakpoint(addr: Int) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/DebuggerWindow.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.ui 2 | 3 | import com.badlogic.gdx.scenes.scene2d.ui.Table 4 | import com.kotcrab.vis.ui.widget.* 5 | import com.kotcrab.vis.ui.widget.file.FileUtils 6 | import com.kotcrab.vis.ui.widget.tabbedpane.Tab 7 | import com.kotcrab.vis.ui.widget.tabbedpane.TabbedPane 8 | import com.kotcrab.vis.ui.widget.tabbedpane.TabbedPaneAdapter 9 | import com.kotcrab.xgbc.Emulator 10 | import com.kotcrab.xgbc.EmulatorMode 11 | import com.kotcrab.xgbc.changed 12 | import com.kotcrab.xgbc.toHex 13 | import com.kotcrab.xgbc.ui.TableBuilder 14 | 15 | /** @author Kotcrab */ 16 | 17 | class DebuggerWindow(val emulator: Emulator) : VisWindow("Debugger") { 18 | init { 19 | defaults().left() 20 | 21 | val tabbedPane = TabbedPane() 22 | val tabbedPaneContent = VisTable() 23 | 24 | tabbedPane.addListener(object : TabbedPaneAdapter() { 25 | override fun switchedTab(tab: Tab?) { 26 | if (tab == null) return 27 | tabbedPaneContent.clearChildren() 28 | tabbedPaneContent.add(tab.contentTable).left().top().grow() 29 | } 30 | }) 31 | 32 | tabbedPane.add(DebuggerTab(emulator)) 33 | tabbedPane.add(MemoryTab(emulator)) 34 | tabbedPane.add(CartridgeInfoTab(emulator)) 35 | tabbedPane.switchTab(0) 36 | 37 | add(tabbedPane.table).growX().row() 38 | add(tabbedPaneContent).size(500.0f, 500.0f) 39 | 40 | pack() 41 | centerWindow() 42 | 43 | emulator.mode = EmulatorMode.DEBUGGING 44 | } 45 | } 46 | 47 | class DebuggerTab(val emulator: Emulator) : Tab(false, false) { 48 | private val table = VisTable(false) 49 | 50 | init { 51 | val opCodesDebuggerTab = OpCodesDebuggerTab(emulator) 52 | 53 | table.left().top() 54 | table.defaults().left() 55 | table.add(opCodesDebuggerTab).growX().row() 56 | 57 | val subTable = VisTable(true) 58 | val cpuDebuggerTab = CpuDebuggerTable(emulator) 59 | val stackPointerScrollPane = VisScrollPane(StackPointerView(emulator, opCodesDebuggerTab)) 60 | stackPointerScrollPane.setScrollingDisabled(true, false) 61 | stackPointerScrollPane.setFlickScroll(false) 62 | stackPointerScrollPane.setFadeScrollBars(false) 63 | subTable.add(cpuDebuggerTab).width(150.0f) 64 | subTable.add(stackPointerScrollPane).height(110.0f).width(150.0f) 65 | table.add(subTable).padTop(10.0f).row() 66 | 67 | val stepButton = VisTextButton("Step") 68 | stepButton.changed { changeEvent, actor -> emulator.step() } 69 | val goToPC = VisTextButton("Show Execution Point") 70 | goToPC.changed { changeEvent, actor -> opCodesDebuggerTab.scrollToExecPoint() } 71 | val resume = VisTextButton("Resume") 72 | resume.changed { changeEvent, actor -> opCodesDebuggerTab.resumeExecution() } 73 | val stop = VisTextButton("Stop") 74 | stop.changed { changeEvent, actor -> opCodesDebuggerTab.stopExecution() } 75 | 76 | table.add(TableBuilder.build(stepButton, goToPC, resume, stop)) 77 | } 78 | 79 | override fun getContentTable(): Table? { 80 | return table 81 | } 82 | 83 | override fun getTabTitle(): String? { 84 | return "Debugger" 85 | } 86 | } 87 | 88 | class CartridgeInfoTab(val emulator: Emulator) : Tab(false, false) { 89 | private val table: VisTable = VisTable(false) 90 | 91 | init { 92 | table.left().top() 93 | table.defaults().left() 94 | table.add(VisLabel("Title: ${emulator.rom.title}")).row() 95 | table.add(VisLabel("GameBoy Color: ${emulator.rom.gameBoyColor}")).row() 96 | table.add(VisLabel("Super GameBoy: ${emulator.rom.superGameBoy}")).row() 97 | table.add(VisLabel("Cartridge Type: ${emulator.rom.cartridgeType}")).row() 98 | table.add(VisLabel("ROM Size: ${FileUtils.readableFileSize(emulator.rom.romSize.toLong())}")).row() 99 | table.add(VisLabel("RAM Size: ${FileUtils.readableFileSize(emulator.rom.ramSize.toLong())}")).row() 100 | table.add(VisLabel("Destination code: ${emulator.rom.destCode}")).row() 101 | } 102 | 103 | override fun getContentTable(): Table? { 104 | return table 105 | } 106 | 107 | override fun getTabTitle(): String? { 108 | return "Cartridge Info" 109 | } 110 | } 111 | 112 | class MemoryTab(val emulator: Emulator) : Tab(false, false) { 113 | private val table: VisTable = VisTable(false) 114 | private val scrollPaneContainer: VisTable = VisTable(false) 115 | private val scrollPane: VisScrollPane = VisScrollPane(table) 116 | 117 | init { 118 | scrollPaneContainer.add(scrollPane).grow() 119 | 120 | scrollPane.setFadeScrollBars(false) 121 | scrollPane.setFlickScroll(false) 122 | 123 | table.left().top() 124 | table.defaults().left() 125 | 126 | var first = true 127 | for (addr in 0x0000..0x100F) { 128 | val line = addr % 0x10 == 0 129 | if (line) { 130 | if (first == false) table.row() 131 | table.add(toHex(addr)) 132 | first = false 133 | } 134 | 135 | table.add(toHex(emulator.read(addr))).width(20.0f) 136 | } 137 | } 138 | 139 | override fun getContentTable(): Table? { 140 | return scrollPaneContainer 141 | } 142 | 143 | override fun getTabTitle(): String? { 144 | return "Memory" 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/EmulatorWindow.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.ui 2 | 3 | import com.badlogic.gdx.graphics.Pixmap 4 | import com.badlogic.gdx.graphics.Texture 5 | import com.badlogic.gdx.graphics.g2d.Batch 6 | import com.badlogic.gdx.graphics.g2d.TextureRegion 7 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable 8 | import com.kotcrab.vis.ui.widget.VisImage 9 | import com.kotcrab.vis.ui.widget.VisWindow 10 | import com.kotcrab.xgbc.Emulator 11 | import com.kotcrab.xgbc.gdx.GdxGpu 12 | import com.kotcrab.xgbc.io.Gpu 13 | import com.kotcrab.xgbc.toUnsignedInt 14 | 15 | /** @author Kotcrab */ 16 | 17 | class EmulatorWindow(val emulator: Emulator) : VisWindow("Emulator") { 18 | private companion object { 19 | val PALETTE_WIDTH = 32 20 | val PALETTE_HEIGHT = 32 21 | } 22 | 23 | private val pixmap = Pixmap(PALETTE_WIDTH * Gpu.TILE_SIZE, PALETTE_HEIGHT * Gpu.TILE_SIZE, Pixmap.Format.RGB888) 24 | private val texture = Texture(pixmap) 25 | 26 | private val gpu = emulator.gpu 27 | private val lcd = emulator.io.lcd 28 | private val gdxGpu = GdxGpu(gpu) 29 | 30 | init { 31 | val img = VisImage(TextureRegionDrawable(TextureRegion(texture))) 32 | add(img).size(256f, 256f) 33 | emulator.lcdTransferHandler = { 34 | val tileMapStart = lcd.getBgTileMapDataAddr() 35 | val patternDataAddr = lcd.getPatternDataAddr() 36 | for ((index, addr) in (tileMapStart..tileMapStart + Gpu.TIME_MAP_DATA_SIZE).withIndex()) { 37 | val tileId = if (patternDataAddr == Gpu.PATTERN_TABLE_1) emulator.read(addr).toUnsignedInt() else emulator.read(addr).toInt() 38 | val column = index / 32 39 | val row = index - column * 32 40 | if (column == lcd.scanLine) 41 | gdxGpu.drawPatternTileToPixmap(pixmap, row * Gpu.TILE_SIZE, column * Gpu.TILE_SIZE, patternDataAddr, tileId) 42 | } 43 | } 44 | 45 | pack() 46 | setPosition(60f, 60f) 47 | } 48 | 49 | override fun draw(batch: Batch, parentAlpha: Float) { 50 | super.draw(batch, parentAlpha) 51 | texture.draw(pixmap, 0, 0) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/OpCodeLine.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.ui 2 | 3 | import com.badlogic.gdx.Input 4 | import com.badlogic.gdx.scenes.scene2d.InputEvent 5 | import com.badlogic.gdx.scenes.scene2d.InputListener 6 | import com.badlogic.gdx.scenes.scene2d.Touchable 7 | import com.badlogic.gdx.scenes.scene2d.utils.ClickListener 8 | import com.kotcrab.vis.ui.widget.VisImage 9 | import com.kotcrab.vis.ui.widget.VisLabel 10 | import com.kotcrab.vis.ui.widget.VisTable 11 | import com.kotcrab.xgbc.Assets 12 | import com.kotcrab.xgbc.Emulator 13 | import com.kotcrab.xgbc.Icon 14 | import com.kotcrab.xgbc.cpu.Instr 15 | import com.kotcrab.xgbc.toHex 16 | 17 | /** @author Kotcrab */ 18 | class OpCodeLine(private val debuggerPopupMenu: DebuggerPopupMenu) : VisTable(false) { 19 | private val icon = VisImage() 20 | private val label = VisLabel("", "small") 21 | 22 | private var breakpoint = false 23 | private var currentLine = false 24 | 25 | var addr: Int = 0 26 | private set 27 | 28 | init { 29 | touchable = Touchable.enabled 30 | 31 | add(icon).size(16.0f, 16.0f).padRight(2.0f) 32 | add(label) 33 | 34 | icon.addListener(object : ClickListener() { 35 | override fun clicked(event: InputEvent?, x: Float, y: Float) { 36 | setBreakpoint(!breakpoint) 37 | } 38 | }) 39 | 40 | addListener(object : InputListener() { 41 | override fun touchDown(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int): Boolean { 42 | return true 43 | } 44 | 45 | override fun touchUp(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int) { 46 | super.touchUp(event, x, y, pointer, button) 47 | if (event == null) return 48 | if (button == Input.Buttons.RIGHT) { 49 | debuggerPopupMenu.ctxAddr = addr 50 | debuggerPopupMenu.showMenu(stage, event.stageX, event.stageY) 51 | } 52 | } 53 | }) 54 | } 55 | 56 | fun parse(emulator: Emulator, addr: Int, instr: Instr?) { 57 | this.addr = addr 58 | 59 | if (instr == null) { 60 | label.setText("Unsupported opcode at ${toHex(addr)}") 61 | return 62 | } 63 | 64 | var evaluatedName = instr.mnemonic 65 | if (addr < 0xFFFF) { 66 | evaluatedName = evaluatedName.replace("d8", toHex(emulator.read(addr + 1))) 67 | evaluatedName = evaluatedName.replace("a8", toHex(emulator.read(addr + 1))) 68 | evaluatedName = evaluatedName.replace("r8", toHex(emulator.read(addr + 1))) 69 | } 70 | if (addr < 0xFFFF - 1) { 71 | evaluatedName = evaluatedName.replace("a16", toHex(emulator.read16(addr + 1))) 72 | evaluatedName = evaluatedName.replace("d16", toHex(emulator.read16(addr + 1))) 73 | } 74 | 75 | if (evaluatedName.equals(instr.mnemonic)) 76 | label.setText("${toHex(addr)}: ${instr.mnemonic}") 77 | else 78 | label.setText("${toHex(addr)}: $evaluatedName [${instr.mnemonic}]") 79 | } 80 | 81 | fun setBreakpoint(breakpoint: Boolean) { 82 | this.breakpoint = breakpoint 83 | if (breakpoint) { 84 | debuggerPopupMenu.listener.addBreakpoint(addr) 85 | } else { 86 | debuggerPopupMenu.listener.removeBreakpoint(addr) 87 | } 88 | updateIcon() 89 | } 90 | 91 | fun setCurrentLine(currentLine: Boolean) { 92 | this.currentLine = currentLine 93 | updateIcon() 94 | } 95 | 96 | private fun updateIcon() { 97 | if (currentLine && breakpoint) 98 | setIcon(Icon.BREAKPOINT_CURRENT_LINE) 99 | else if (currentLine) 100 | setIcon(Icon.CURRENT_LINE) 101 | else if (breakpoint) 102 | setIcon(Icon.BREAKPOINT) 103 | else 104 | icon.drawable = null 105 | } 106 | 107 | private fun setIcon(iconType: Icon) { 108 | icon.drawable = Assets.get(iconType) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/OpCodesDebuggerTab.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.ui 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.Input 5 | import com.badlogic.gdx.graphics.g2d.Batch 6 | import com.badlogic.gdx.math.Vector2 7 | import com.kotcrab.vis.ui.VisUI 8 | import com.kotcrab.vis.ui.util.value.PrefHeightIfVisibleValue 9 | import com.kotcrab.vis.ui.widget.* 10 | import com.kotcrab.vis.ui.widget.spinner.IntSpinnerModel 11 | import com.kotcrab.vis.ui.widget.spinner.Spinner 12 | import com.kotcrab.xgbc.* 13 | import com.kotcrab.xgbc.cpu.Instr 14 | import com.badlogic.gdx.utils.Array as GdxArray 15 | 16 | /** @author Kotcrab */ 17 | class OpCodesDebuggerTab(val emulator: Emulator) : VisTable(false), DebuggerPopupMenu.Listener { 18 | val tmpVector = Vector2() 19 | 20 | val chunkSize = 1027 21 | val chunks = arrayOfNulls(0xFFFF / chunkSize + 1) 22 | var activeChunk: Chunk? = null 23 | 24 | var mode = Mode.INTERACTIVE 25 | private set 26 | var execStopAddr = -1 27 | private set 28 | val breakpoints = GdxArray() 29 | 30 | val chunkContainer = VisTable() 31 | val scrollPane: VisScrollPane 32 | val chunkInfoLabel = VisLabel() 33 | val chunkSelector = Spinner("Fragment", IntSpinnerModel(0, 0, chunks.size - 1)) 34 | private var currentLine: OpCodeLine? = null 35 | 36 | val goToAddressField = VisValidatableTextField("") 37 | 38 | private val debuggerPopupMenu = DebuggerPopupMenu(this) 39 | 40 | init { 41 | left().top() 42 | defaults().left() 43 | 44 | chunkContainer.left() 45 | 46 | scrollPane = object : VisScrollPane(chunkContainer) { 47 | override fun getMouseWheelY(): Float { 48 | return 60.0f 49 | } 50 | 51 | override fun getPrefHeight(): Float { 52 | return chunkSize * 19.0f 53 | } 54 | } 55 | 56 | scrollPane.setFlickScroll(false) 57 | scrollPane.setScrollingDisabled(true, false) 58 | scrollPane.setFadeScrollBars(false) 59 | scrollPane.setOverscroll(false, false) 60 | scrollPane.setSmoothScrolling(false) 61 | add(scrollPane).growX().row() 62 | add(TableBuilder.build(VisUI.getSizes().spacingRight.toInt(), chunkSelector, chunkInfoLabel, VisLabel("Go to: "), goToAddressField)) 63 | 64 | chunkSelector.isProgrammaticChangeEvents = false 65 | chunkSelector.changed { changeEvent, actor -> 66 | switchChunk((chunkSelector.model as IntSpinnerModel).value) 67 | } 68 | 69 | emulator.addDebuggerListener(object : DebuggerListener { 70 | override fun onCpuTick(oldPc: Int, pc: Int) { 71 | var stopExecution = false 72 | if (execStopAddr == pc) { 73 | execStopAddr = -1 74 | stopExecution = true 75 | } 76 | 77 | if (breakpoints.contains(emulator.cpu.pc, false)) { 78 | stopExecution = true 79 | } 80 | 81 | if (stopExecution) stopExecution() 82 | updateCurrentLine(pc) 83 | } 84 | 85 | override fun onMemoryWrite(addr: Int, value: Byte) { 86 | if (mode == Mode.INTERACTIVE) { 87 | reparseChunks() 88 | } 89 | } 90 | }) 91 | 92 | goToAddressField.setFocusTraversal(false) 93 | goToAddressField.setProgrammaticChangeEvents(false) 94 | goToAddressField.textFieldFilter = VisTextField.TextFieldFilter { textField, c -> Character.isDigit(c) || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F' } 95 | goToAddressField.maxLength = 4 96 | goToAddressField.changed { changeEvent, actor -> 97 | if (goToAddressField.text.equals("") == false) { 98 | val addr = Integer.parseInt(goToAddressField.text, 16) 99 | scrollToAddr(addr) 100 | } 101 | } 102 | 103 | var nextParseAddr = 0 104 | for (index in chunks.indices) { 105 | val chunk = Chunk(index * chunkSize, nextParseAddr) 106 | chunks[index] = chunk 107 | nextParseAddr = chunk.parseEndAddr 108 | } 109 | switchChunk(0) 110 | } 111 | 112 | fun scrollToExecPoint() { 113 | scrollToAddr(emulator.cpu.pc) 114 | } 115 | 116 | private fun switchChunk(index: Int) { 117 | val chunk = chunks[index]!! 118 | if (chunk == activeChunk) return 119 | reparseChunks() 120 | chunk.parse() 121 | activeChunk = chunk 122 | chunkContainer.clearChildren() 123 | chunkContainer.add(chunk) 124 | scrollPane.validate() 125 | 126 | chunkInfoLabel.setText("Showing ${toHex(chunk.chunkBeginAddr)}-${toHex(Math.min(0xFFFF, chunk.chunkBeginAddr + chunkSize - 1))}") 127 | (chunkSelector.model as IntSpinnerModel).value = index 128 | } 129 | 130 | private fun scrollToAddr(addr: Int) { 131 | val targetChunk = getChunk(addr)!! 132 | if (targetChunk.uiReady == false) { 133 | targetChunk.parse() 134 | } 135 | val currentLine = getOpCodeLine(addr) 136 | scrollTo(currentLine) 137 | } 138 | 139 | private fun scrollTo(line: OpCodeLine?) { 140 | if (line == null) return 141 | 142 | switchChunk(line.addr / chunkSize) 143 | tmpVector.set(0.0f, 0.0f) 144 | line.localToParentCoordinates(tmpVector) 145 | scrollPane.scrollTo(tmpVector.x, tmpVector.y, line.width, line.height, false, true) 146 | } 147 | 148 | private fun getChunk(addr: Int): Chunk? { 149 | val chunkIndex = addr / chunkSize 150 | return chunks[chunkIndex] 151 | } 152 | 153 | private fun getOpCodeLine(addr: Int): OpCodeLine? { 154 | return getChunk(addr)?.getOpCodeLine(addr) 155 | } 156 | 157 | override fun draw(batch: Batch?, parentAlpha: Float) { 158 | super.draw(batch, parentAlpha) 159 | 160 | if (Gdx.input.isKeyJustPressed(Input.Keys.F3)) { 161 | emulator.step() 162 | } 163 | 164 | if (Gdx.input.isKeyJustPressed(Input.Keys.F5)) { 165 | //dump test result 166 | for (i in 0xA004..0xC000) { 167 | val value = emulator.readInt(i) 168 | if (value == 0) { 169 | println() 170 | break 171 | } 172 | print(value.toChar()) 173 | } 174 | } 175 | 176 | if (mode == Mode.RUNNING) { 177 | //if emulator step caused debugger to go into interactive mode stop update 178 | emulator.update(updateBreaker = { mode == Mode.INTERACTIVE }) 179 | } 180 | } 181 | 182 | override fun runToLine(ctxAddr: Int) { 183 | execStopAddr = ctxAddr 184 | mode = Mode.RUNNING 185 | } 186 | 187 | override fun addBreakpoint(addr: Int) { 188 | breakpoints.add(addr) 189 | } 190 | 191 | override fun removeBreakpoint(addr: Int) { 192 | breakpoints.removeValue(addr, false) 193 | } 194 | 195 | fun resumeExecution() { 196 | mode = Mode.RUNNING 197 | currentLine?.setCurrentLine(false) 198 | } 199 | 200 | fun stopExecution() { 201 | execStopAddr = -1 202 | mode = Mode.INTERACTIVE 203 | reparseChunks() 204 | Gdx.app.postRunnable { 205 | Gdx.app.postRunnable { scrollToExecPoint() } 206 | } 207 | updateCurrentLine(emulator.cpu.pc) 208 | } 209 | 210 | private fun updateCurrentLine(pc: Int) { 211 | if (mode == Mode.INTERACTIVE) { 212 | currentLine?.setCurrentLine(false) 213 | val targetChunk = getChunk(pc)!! 214 | if (targetChunk.uiReady == false) { 215 | targetChunk.parse() 216 | } 217 | currentLine = getOpCodeLine(pc) 218 | currentLine?.setCurrentLine(true) 219 | scrollTo(currentLine) 220 | } 221 | } 222 | 223 | private fun reparseChunks() { 224 | var nextParseAddr = 0 225 | for (index in chunks.indices) { 226 | val chunk = chunks[index]!! 227 | chunk.parse(nextParseAddr, chunk == activeChunk) 228 | nextParseAddr = chunk.parseEndAddr 229 | } 230 | } 231 | 232 | inner class Chunk(val chunkBeginAddr: Int, private var parseBeginAddr: Int) : VisTable(false) { 233 | var uiReady = false 234 | private set 235 | private val lines = arrayOfNulls(chunkSize) 236 | 237 | var parseEndAddr: Int = 0 238 | private set 239 | 240 | init { 241 | left().top() 242 | defaults().left() 243 | 244 | parse(parseBeginAddr, false) 245 | } 246 | 247 | fun parse() { 248 | parse(this.parseBeginAddr, true) 249 | } 250 | 251 | fun parse(parseBeginAddr: Int, updateUI: Boolean) { 252 | this.parseBeginAddr = parseBeginAddr 253 | 254 | if (updateUI && uiReady == false) prepareUI() 255 | for (i in 0x00..chunkSize - 1) { 256 | lines[i]?.isVisible = false 257 | } 258 | 259 | var addr = parseBeginAddr 260 | while (addr < chunkBeginAddr + chunkSize) { 261 | var opcode = emulator.read(addr) 262 | var opcodeInt = opcode.toUnsignedInt() 263 | 264 | var instr: Instr? 265 | if (opcodeInt == 0xCB) { 266 | opcode = emulator.read(addr + 1) 267 | opcodeInt = opcode.toUnsignedInt() 268 | instr = emulator.cpu.extOp[opcodeInt] 269 | } else { 270 | instr = emulator.cpu.op[opcodeInt] 271 | } 272 | 273 | if (updateUI) updateUI(emulator, addr, instr) 274 | 275 | if (instr != null) { 276 | addr += instr.len 277 | } else { 278 | addr += 1 279 | } 280 | 281 | if (addr > 0xFFFF) { 282 | break 283 | } 284 | } 285 | parseEndAddr = addr 286 | } 287 | 288 | fun prepareUI() { 289 | for (i in 0x00..chunkSize - 1) { 290 | lines[i] = OpCodeLine(debuggerPopupMenu) 291 | add(lines[i]).height(PrefHeightIfVisibleValue.INSTANCE).row() 292 | } 293 | uiReady = true 294 | } 295 | 296 | private fun updateUI(emulator: Emulator, addr: Int, instr: Instr?) { 297 | lines[addr - chunkBeginAddr]?.parse(emulator, addr, instr) 298 | lines[addr - chunkBeginAddr]?.isVisible = true 299 | } 300 | 301 | fun getOpCodeLine(addr: Int): OpCodeLine? { 302 | if (uiReady == false) throw IllegalStateException("Chunk UI not ready") 303 | return lines[addr - chunkBeginAddr] 304 | } 305 | } 306 | 307 | enum class Mode { 308 | RUNNING, INTERACTIVE 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/StackPointerView.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.ui 2 | 3 | import com.kotcrab.vis.ui.util.value.PrefHeightIfVisibleValue 4 | import com.kotcrab.vis.ui.widget.VisLabel 5 | import com.kotcrab.vis.ui.widget.VisTable 6 | import com.kotcrab.xgbc.DebuggerListener 7 | import com.kotcrab.xgbc.Emulator 8 | import com.kotcrab.xgbc.toHex 9 | 10 | /** @author Kotcrab */ 11 | class StackPointerView(val emulator: Emulator, opCodesDebuggerTab: OpCodesDebuggerTab) : VisTable() { 12 | private val numValuesShown = 7 //must be odd 13 | private val spOffset = (numValuesShown - 1) 14 | private val labels = arrayOfNulls(numValuesShown) 15 | 16 | private var currentSp = -1 17 | 18 | init { 19 | left().top() 20 | defaults().left() 21 | 22 | add("Stack Pointer", "small").row() 23 | for (i in labels.indices) { 24 | labels[i] = VisLabel("", "small") 25 | add(labels[i]).height(PrefHeightIfVisibleValue.INSTANCE).row() 26 | } 27 | 28 | emulator.addDebuggerListener(object : DebuggerListener { 29 | override fun onCpuTick(oldPc: Int, pc: Int) { 30 | if (opCodesDebuggerTab.mode == OpCodesDebuggerTab.Mode.RUNNING) return 31 | if (currentSp != emulator.cpu.sp) { 32 | currentSp = emulator.cpu.sp 33 | updateLabels() 34 | } 35 | } 36 | }) 37 | } 38 | 39 | private fun updateLabels() { 40 | val sp = emulator.cpu.sp 41 | var index = 0 42 | for (addr in sp + spOffset downTo sp - spOffset step 2) { 43 | val label = labels[index]!! 44 | if (addr < 0xFFFF && addr > 0x0000) { 45 | label.setText(toHex(addr) + ": " + toHex(emulator.read16(addr))) 46 | label.isVisible = true 47 | } else { 48 | label.setText("") 49 | label.isVisible = false 50 | } 51 | index++ 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/TableBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 See AUTHORS file. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kotcrab.xgbc.ui 18 | 19 | import com.badlogic.gdx.scenes.scene2d.Actor 20 | import com.kotcrab.vis.ui.widget.VisTable 21 | 22 | /** 23 | * Table in tables. Simplified. 24 | * @author Kotcrab 25 | */ 26 | object TableBuilder { 27 | fun build(text: String, labelWidth: Int, actor: Actor): VisTable { 28 | val table = VisTable(true) 29 | table.add(text).width(labelWidth.toFloat()) 30 | table.add(actor) 31 | return table 32 | } 33 | 34 | fun build(vararg actors: Actor): VisTable { 35 | return build(VisTable(true), *actors) 36 | } 37 | 38 | fun build(rightSpacing: Int, vararg actors: Actor): VisTable { 39 | val table = VisTable(true) 40 | table.defaults().spaceRight(rightSpacing.toFloat()) 41 | return build(table, *actors) 42 | } 43 | 44 | fun build(target: VisTable, vararg actors: Actor): VisTable { 45 | for (actor in actors) target.add(actor) 46 | return target 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/kotlin/com/kotcrab/xgbc/ui/VRAMWindow.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.ui 2 | 3 | import com.badlogic.gdx.graphics.Pixmap 4 | import com.badlogic.gdx.graphics.Texture 5 | import com.badlogic.gdx.graphics.g2d.Batch 6 | import com.badlogic.gdx.graphics.g2d.TextureRegion 7 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable 8 | import com.kotcrab.vis.ui.widget.VisImage 9 | import com.kotcrab.vis.ui.widget.VisWindow 10 | import com.kotcrab.xgbc.Emulator 11 | import com.kotcrab.xgbc.Interrupt 12 | import com.kotcrab.xgbc.gdx.GdxGpu 13 | import com.kotcrab.xgbc.io.Gpu 14 | 15 | /** @author Kotcrab */ 16 | 17 | class VRAMWindow(val emulator: Emulator) : VisWindow("VRAM") { 18 | private companion object { 19 | val PALETTE_WIDTH = 16 20 | val PALETTE_HEIGHT = 16 21 | } 22 | 23 | private val gdxGpu = GdxGpu(emulator.gpu) 24 | 25 | private val pixmap1 = Pixmap(PALETTE_WIDTH * Gpu.TILE_SIZE, PALETTE_HEIGHT * Gpu.TILE_SIZE, Pixmap.Format.RGB888) 26 | private val pixmap0 = Pixmap(PALETTE_WIDTH * Gpu.TILE_SIZE, PALETTE_HEIGHT * Gpu.TILE_SIZE, Pixmap.Format.RGB888) 27 | private val texture1 = Texture(pixmap1) 28 | private val texture0 = Texture(pixmap0) 29 | 30 | var frameUsed = false 31 | 32 | init { 33 | val img1 = VisImage(TextureRegionDrawable(TextureRegion(texture1))) 34 | add(img1).size(256f, 256f) 35 | row() 36 | val img0 = VisImage(TextureRegionDrawable(TextureRegion(texture0))) 37 | add(img0).size(256f, 256f) 38 | pack() 39 | 40 | emulator.interruptHandlers.add { interrupt -> 41 | if (interrupt == Interrupt.VBLANK && frameUsed == false) { 42 | frameUsed = true 43 | for (row in 0..PALETTE_WIDTH) { 44 | for (column in 0..PALETTE_HEIGHT) { 45 | gdxGpu.drawPatternTileToPixmap(pixmap0, row * Gpu.TILE_SIZE, column * Gpu.TILE_SIZE, Gpu.PATTERN_TABLE_0, column * 0x10 + row) 46 | gdxGpu.drawPatternTileToPixmap(pixmap1, row * Gpu.TILE_SIZE, column * Gpu.TILE_SIZE, Gpu.PATTERN_TABLE_1, column * 0x10 + row) 47 | } 48 | } 49 | texture0.draw(pixmap0, 0, 0) 50 | texture1.draw(pixmap1, 0, 0) 51 | } 52 | } 53 | 54 | setPosition(1000f, 30f) 55 | } 56 | 57 | override fun draw(batch: Batch, parentAlpha: Float) { 58 | super.draw(batch, parentAlpha) 59 | frameUsed = false 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/resources/gfx/ui.atlas: -------------------------------------------------------------------------------- 1 | 2 | ui.png 3 | size: 64,32 4 | format: RGBA8888 5 | filter: Nearest,Nearest 6 | repeat: none 7 | breakpoint 8 | rotate: false 9 | xy: 2, 2 10 | size: 16, 16 11 | orig: 16, 16 12 | offset: 0, 0 13 | index: -1 14 | breakpoint-current-line 15 | rotate: false 16 | xy: 20, 2 17 | size: 16, 16 18 | orig: 16, 16 19 | offset: 0, 0 20 | index: -1 21 | current-line 22 | rotate: false 23 | xy: 38, 2 24 | size: 16, 16 25 | orig: 16, 16 26 | offset: 0, 0 27 | index: -1 28 | -------------------------------------------------------------------------------- /src/main/resources/gfx/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kotcrab/xgbc/25d2623acf0f1e87cb7a2db61c26af4d2d74f735/src/main/resources/gfx/ui.png -------------------------------------------------------------------------------- /src/test/kotlin/com/kotcrab/xgbc/test/TestCbOpCodesTimings.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.test 2 | 3 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Files 4 | import com.kotcrab.xgbc.Emulator 5 | import com.kotcrab.xgbc.toHex 6 | import org.junit.Assert 7 | import org.junit.Before 8 | import org.junit.Test 9 | 10 | /** @author Kotcrab */ 11 | class TestCbOpCodesTimings { 12 | lateinit var emulator: Emulator 13 | 14 | @Before 15 | fun setUp() { 16 | emulator = Emulator(Lwjgl3Files().internal("test_rom.gb")) 17 | } 18 | 19 | @Test 20 | fun testCbTimings() { 21 | val instrTiming = arrayOf( 22 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 23 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 24 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 25 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 26 | 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 27 | 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 28 | 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 29 | 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 30 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 31 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 32 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 33 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 34 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 35 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 36 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 37 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2) 38 | 39 | for (i in 0x00..0xFF - 1) { 40 | val instrCycles = instrTiming[i] 41 | val instr = emulator.cpu.extOp[i] 42 | if (instrCycles == 0 || instr == null) 43 | continue 44 | 45 | Assert.assertEquals("Invalid CB op code timing: OP: ${toHex(i)}, expected $instrCycles MC", instr.realCycles, instrCycles * 4) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/kotlin/com/kotcrab/xgbc/test/TestConditionalOpCodesTimings.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.test 2 | 3 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Files 4 | import com.kotcrab.xgbc.Emulator 5 | import com.kotcrab.xgbc.cpu.JmpInstr 6 | import com.kotcrab.xgbc.toHex 7 | import org.junit.Assert 8 | import org.junit.Before 9 | import org.junit.Test 10 | 11 | /** @author Kotcrab */ 12 | class TestConditionalOpCodesTimings { 13 | lateinit var emulator: Emulator 14 | 15 | @Before 16 | fun setUp() { 17 | emulator = Emulator(Lwjgl3Files().internal("test_rom.gb")) 18 | } 19 | 20 | @Test 21 | fun testConditionalTimings() { 22 | val instrTiming = arrayOf( 23 | 1, 3, 2, 2, 1, 1, 2, 1, 5, 2, 2, 2, 1, 1, 2, 1, 24 | 0, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, 25 | 3, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, 26 | 3, 3, 2, 2, 3, 3, 3, 1, 3, 2, 2, 2, 1, 1, 2, 1, 27 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 28 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 29 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 30 | 2, 2, 2, 2, 2, 2, 0, 2, 1, 1, 1, 1, 1, 1, 2, 1, 31 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 32 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 33 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 34 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 35 | 5, 3, 4, 4, 6, 4, 2, 4, 5, 4, 4, 0, 6, 6, 2, 4, 36 | 5, 3, 4, 0, 6, 4, 2, 4, 5, 4, 4, 0, 6, 0, 2, 4, 37 | 3, 3, 2, 0, 0, 4, 2, 4, 4, 1, 4, 0, 0, 0, 2, 4, 38 | 3, 3, 2, 1, 0, 4, 2, 4, 3, 2, 4, 1, 0, 0, 2, 4) 39 | 40 | for (i in 0x00..0xFF - 1) { 41 | val instrCycles = instrTiming[i] 42 | val instr = emulator.cpu.op[i] 43 | if (instrCycles == 0 || instr == null) 44 | continue 45 | 46 | var cycles = instr.realCycles 47 | if (instr is JmpInstr) { 48 | cycles = instr.cyclesIfTaken + instr.internalCycles 49 | } 50 | 51 | Assert.assertEquals("Invalid op code conditional timing: OP: ${toHex(i)}, expected $instrCycles MC", cycles, instrCycles * 4) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/kotlin/com/kotcrab/xgbc/test/TestOpCodesLengths.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.test 2 | 3 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Files 4 | import com.kotcrab.xgbc.Emulator 5 | import com.kotcrab.xgbc.toHex 6 | import org.junit.Assert 7 | import org.junit.Before 8 | import org.junit.Test 9 | 10 | /** @author Kotcrab */ 11 | class TestOpCodesLengths { 12 | lateinit var emulator: Emulator 13 | 14 | @Before 15 | fun setUp() { 16 | emulator = Emulator(Lwjgl3Files().internal("test_rom.gb")) 17 | } 18 | 19 | @Test 20 | fun testLengths() { 21 | val instrLength = arrayOf( 22 | 1, 3, 1, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, 1, 2, 1, 23 | 0, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, 24 | 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, 25 | 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, 26 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 32 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 33 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34 | 1, 1, 3, 3, 3, 1, 2, 1, 1, 1, 3, 0, 3, 3, 2, 1, 35 | 1, 1, 3, 0, 3, 1, 2, 1, 1, 1, 3, 0, 3, 0, 2, 1, 36 | 2, 1, 1, 0, 0, 1, 2, 1, 2, 1, 3, 0, 0, 0, 2, 1, 37 | 2, 1, 1, 1, 0, 1, 2, 1, 2, 1, 3, 1, 0, 0, 2, 1) 38 | 39 | for (i in 0x00..0xFF - 1) { 40 | val length = instrLength[i] 41 | val instr = emulator.cpu.op[i] 42 | if (length == 0 || instr == null) 43 | continue 44 | 45 | Assert.assertEquals("Invalid op code length: OP: ${toHex(i)}, expected $length", instr.len, length) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/kotlin/com/kotcrab/xgbc/test/TestOpCodesTimings.kt: -------------------------------------------------------------------------------- 1 | package com.kotcrab.xgbc.test 2 | 3 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Files 4 | import com.kotcrab.xgbc.Emulator 5 | import com.kotcrab.xgbc.toHex 6 | import org.junit.Assert 7 | import org.junit.Before 8 | import org.junit.Test 9 | 10 | /** @author Kotcrab */ 11 | class TestOpCodesTimings { 12 | lateinit var emulator: Emulator 13 | 14 | @Before 15 | fun setUp() { 16 | emulator = Emulator(Lwjgl3Files().internal("test_rom.gb")) 17 | } 18 | 19 | @Test 20 | fun testTimings() { 21 | val instrTiming = arrayOf( 22 | 1, 3, 2, 2, 1, 1, 2, 1, 5, 2, 2, 2, 1, 1, 2, 1, 23 | 0, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, 24 | 2, 3, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 2, 1, 25 | 2, 3, 2, 2, 3, 3, 3, 1, 2, 2, 2, 2, 1, 1, 2, 1, 26 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 27 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 28 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 29 | 2, 2, 2, 2, 2, 2, 0, 2, 1, 1, 1, 1, 1, 1, 2, 1, 30 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 31 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 32 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 33 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 34 | 2, 3, 3, 4, 3, 4, 2, 4, 2, 4, 3, 0, 3, 6, 2, 4, 35 | 2, 3, 3, 0, 3, 4, 2, 4, 2, 4, 3, 0, 3, 0, 2, 4, 36 | 3, 3, 2, 0, 0, 4, 2, 4, 4, 1, 4, 0, 0, 0, 2, 4, 37 | 3, 3, 2, 1, 0, 4, 2, 4, 3, 2, 4, 1, 0, 0, 2, 4) 38 | 39 | for (i in 0x00..0xFF - 1) { 40 | val instrCycles = instrTiming[i] 41 | val instr = emulator.cpu.op[i] 42 | if (instrCycles == 0 || instr == null) 43 | continue 44 | 45 | Assert.assertEquals("Invalid op code timing: OP: ${toHex(i)}, expected $instrCycles MC", instr.realCycles, instrCycles * 4) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/kotlin/com/kotcrab/xgbc/test/TestUtils.kt: -------------------------------------------------------------------------------- 1 | import com.kotcrab.xgbc.* 2 | import org.junit.Assert.* 3 | import org.junit.Test 4 | 5 | class TestUtils { 6 | @Test 7 | fun testIsBitSet() { 8 | val byte = 0b01100111.toByte() 9 | assertTrue(byte.isBitSet(0)) 10 | assertTrue(byte.isBitSet(1)) 11 | assertTrue(byte.isBitSet(2)) 12 | assertFalse(byte.isBitSet(3)) 13 | assertFalse(byte.isBitSet(4)) 14 | assertTrue(byte.isBitSet(5)) 15 | assertTrue(byte.isBitSet(6)) 16 | assertFalse(byte.isBitSet(7)) 17 | } 18 | 19 | @Test 20 | fun testSetBit() { 21 | val byte = 0b0000.toByte() 22 | assertEquals(byte.setBit(1).setBit(3), 0b1010.toByte()) 23 | } 24 | 25 | @Test 26 | fun testResetBit() { 27 | val byte = 0b1111.toByte() 28 | assertEquals(byte.resetBit(0).resetBit(2), 0b1010.toByte()) 29 | } 30 | 31 | @Test 32 | fun testToggleBit() { 33 | val byte = 0b0011.toByte() 34 | assertEquals(byte.toggleBit(0).toggleBit(1).toggleBit(2).toggleBit(3), 0b1100.toByte()) 35 | assertEquals(byte.toggleBit(0).toggleBit(0), 0b0011.toByte()) 36 | } 37 | 38 | @Test 39 | fun testSetBitState() { 40 | val byte = 0b0011.toByte() 41 | assertEquals(byte.setBitState(0, true).setBitState(0, true).setBitState(2, true), 0b0111.toByte()) 42 | } 43 | 44 | @Test(expected = IllegalArgumentException::class) 45 | fun testIsBitSetException() { 46 | val byte = 0b01100111.toByte() 47 | byte.isBitSet(8) 48 | } 49 | 50 | @Test(expected = IllegalArgumentException::class) 51 | fun testSetBitException() { 52 | val byte = 0b01100111.toByte() 53 | byte.setBit(8) 54 | } 55 | 56 | @Test(expected = IllegalArgumentException::class) 57 | fun testResetBitException() { 58 | val byte = 0b01100111.toByte() 59 | byte.resetBit(8) 60 | } 61 | 62 | @Test 63 | fun testByteRotate() { 64 | val byte = 0b00010111.toByte() 65 | assertEquals(byte.rotateLeft(1), 0b00101110.toByte()) 66 | assertEquals(byte.rotateRight(1), 0b10001011.toByte()) 67 | assertEquals(byte.rotateLeft(3), 0b10111000.toByte()) 68 | assertEquals(byte.rotateRight(3), 0b11100010.toByte()) 69 | 70 | val byte2 = 0b11100111.toByte() 71 | assertEquals(byte2.rotateLeft(1), 0b11001111.toByte()) 72 | assertEquals(byte2.rotateRight(1), 0b11110011.toByte()) 73 | } 74 | 75 | @Test 76 | fun testBooleanToInt() { 77 | assertTrue(true.toInt() == 1) 78 | assertTrue(false.toInt() == 0) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/resources/test_rom.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kotcrab/xgbc/25d2623acf0f1e87cb7a2db61c26af4d2d74f735/src/test/resources/test_rom.gb --------------------------------------------------------------------------------