commit 43fd2f33a6b0856285e03817448000762184e276 Author: baldeau Date: Sat Aug 31 23:57:44 2024 +0200 initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..78c6dde --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fa6b67 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..5e2646b --- /dev/null +++ b/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "b0850beeb25f6d5b10426284f506557f66181b36" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + - platform: ios + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..27c2173 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# dartboy + +Gameboy Emulator written purely in Dart. + + + + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..1b1539a --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,58 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") +if (flutterVersionCode == null) { + flutterVersionCode = "1" +} + +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" +} + +android { + namespace = "com.example.dartboy" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.dartboy" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5111c4c --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/dashboy/MainActivity.kt b/android/app/src/main/kotlin/com/example/dashboy/MainActivity.kt new file mode 100644 index 0000000..fbd1a04 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/dashboy/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.dartboy + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..3b5b324 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e1ca574 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..536165d --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/docs/assets/img/screenshot1.png b/docs/assets/img/screenshot1.png new file mode 100644 index 0000000..8fb7639 Binary files /dev/null and b/docs/assets/img/screenshot1.png differ diff --git a/docs/assets/img/screenshot2.png b/docs/assets/img/screenshot2.png new file mode 100644 index 0000000..363f322 Binary files /dev/null and b/docs/assets/img/screenshot2.png differ diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..d97f17e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d589385 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = T67F6A44RM; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = T67F6A44RM; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = T67F6A44RM; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..9074fee --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..67780ca --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Dartboy + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + dartboy + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + \ No newline at end of file diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/emulator/bus.dart b/lib/emulator/bus.dart new file mode 100644 index 0000000..d208df9 --- /dev/null +++ b/lib/emulator/bus.dart @@ -0,0 +1,248 @@ +import 'package:dartboy/emulator/joypad.dart'; +import 'package:dartboy/emulator/mbc.dart'; +import 'package:dartboy/emulator/ppu.dart'; +import 'package:dartboy/emulator/timer.dart'; +import 'package:dartboy/emulator/utils.dart'; + +class Ie extends BitFieldU8 { + Ie() : super({}); + + Ie.fromU8(int u8) : super.fromU8(u8); + + Bit get vBlank => value(0); + Bit get lcdStat => value(1); + Bit get timer => value(2); + Bit get serial => value(3); + Bit get joypad => value(4); +} + +class Bus { + Bus(this.ppu, this.mbc); + + Ppu ppu = Ppu(); + Joypad joypad = Joypad(); + Timer timer = Timer(); + + List ram = List.filled(0x8000, 0); + List hram = List.filled(0x0080, 0); + Mbc mbc; + + Ie ie = Ie(); + + bool _prevSerial = false; + bool _intSerial = false; + + void tick() { + ppu.tick(); + timer.tick(); + } + + bool get irqVBlank => ppu.intVBlank; + set irqVBlank(bool val) { + ppu.intVBlank = val; + } + + bool get irqLcdStat => ppu.intLcdStat; + set irqLcdStat(bool val) { + ppu.intLcdStat = val; + } + + bool get irqTimer => timer.interrupt; + set irqTimer(bool val) { + timer.interrupt = val; + } + + bool get irqSerial => _intSerial; + set irqSerial(bool val) { + _intSerial = val; + } + + bool get irqJoypad => joypad.interrupt; + set irqJoypad(bool val) { + joypad.interrupt = val; + } + + int read(int addr) { + if (0x0000 <= addr && addr <= 0x7FFF) return mbc.read(addr); + if (0x8000 <= addr && addr <= 0x9FFF) return ppu.read(addr); + if (0xA000 <= addr && addr <= 0xBFFF) return mbc.read(addr); + if (0xC000 <= addr && addr <= 0xDFFF) return ram[addr - 0xC000]; + if (0xE000 <= addr && addr <= 0xFDFF) return ram[addr - 0xE000]; + if (0xFE00 <= addr && addr <= 0xFE9F) return ppu.readOam(addr); + if (0xFEA0 <= addr && addr <= 0xFEFF) return 0; + if (0xFF00 == addr) return joypad.read(); + if (0xFF01 == addr) return readSerial(); + if (0xFF02 == addr) return readSerialCtrl(); + if (0xFF04 == addr) return timer.readDiv(); + if (0xFF05 == addr) return timer.readTima(); + if (0xFF06 == addr) return timer.readTma(); + if (0xFF07 == addr) return timer.readTac(); + if (0xFF0F == addr) return readIrq(); + if (0xFF40 == addr) return ppu.readLcdControl(); + if (0xFF41 == addr) return ppu.readLcdStatus(); + if (0xFF42 == addr) return ppu.readScrollY(); + if (0xFF43 == addr) return ppu.readScrollX(); + if (0xFF44 == addr) return ppu.readLines(); + if (0xFF45 == addr) return ppu.readLineCompare(); + if (0xFF47 == addr) return ppu.readBgPalette(); + if (0xFF48 == addr) return ppu.readObjectPalette0(); + if (0xFF49 == addr) return ppu.readObjectPalette1(); + if (0xFF4A == addr) return ppu.readWindowY(); + if (0xFF4B == addr) return ppu.readWindowX(); + if (0xFF80 <= addr && addr <= 0xFFFE) return hram[addr - 0xFF80]; + if (0xFFFF == addr) return ie.toU8(); + + return 0; + } + + int readWord(int addr) { + final low = read(addr); + final high = read(addr + 1); + + return ((high) << 8) | low; + } + + int readIrq() { + return bitpack([ + false, + false, + false, + joypad.interrupt, + _intSerial, + timer.interrupt, + ppu.intLcdStat, + ppu.intVBlank + ]); + } + + int readSerial() { + return 0; + } + + int readSerialCtrl() { + return 0; + } + + void write(int addr, int val) { + if (0x0000 <= addr && addr <= 0x7FFF) { + mbc.write(addr, val); + } + if (0x8000 <= addr && addr <= 0x9FFF) { + ppu.write(addr, val); + } + if (0xA000 <= addr && addr <= 0xBFFF) { + mbc.write(addr, val); + } + if (0xC000 <= addr && addr <= 0xDFFF) { + ram[addr - 0xC000] = val; + } + if (0xE000 <= addr && addr <= 0xFDFF) { + ram[addr - 0xE000] = val; + } + if (0xFE00 <= addr && addr <= 0xFE9F) { + ppu.writeOam(addr, val); + } + if (0xFEA0 <= addr && addr <= 0xFEFF) { + return; + } + if (0xFF00 == addr) { + joypad.write(val); + } + if (0xFF01 == addr) { + writeSerial(val); + } + if (0xFF02 == addr) { + writeSerialCtrl(val); + } + if (0xFF04 == addr) { + timer.writeDiv(val); + } + if (0xFF05 == addr) { + timer.writeTima(val); + } + if (0xFF06 == addr) { + timer.writeTma(val); + } + if (0xFF07 == addr) { + timer.writeTac(val); + } + if (0xFF0F == addr) { + writeIrq(val); + } + if (0xFF40 == addr) { + ppu.writeLcdControl(val); + } + if (0xFF41 == addr) { + ppu.writeLcdStatus(val); + } + if (0xFF42 == addr) { + ppu.writeScrollY(val); + } + if (0xFF43 == addr) { + ppu.writeScrollX(val); + } + if (0xFF45 == addr) { + ppu.writeLineCompare(val); + } + if (0xFF46 == addr) { + writeDma(val); + } + if (0xFF47 == addr) { + ppu.writeBgPalette(val); + } + if (0xFF48 == addr) { + ppu.writeObjectPalette0(val); + } + if (0xFF49 == addr) { + ppu.writeObjectPalette1(val); + } + if (0xFF4A == addr) { + ppu.writeWindowY(val); + } + if (0xFF4B == addr) { + ppu.writeWindowX(val); + } + if (0xFF80 <= addr && addr <= 0xFFFE) { + hram[addr - 0xFF80] = val; + } + if (0xFFFF == addr) { + ie = Ie.fromU8(val); + } + } + + void writeWord(int addr, int val) { + final low = val.toU8(); + final high = (val >> 8).toU8(); + + write(addr, low); + write(addr + 1, high); + } + + void writeIrq(int val) { + ppu.intVBlank = isSet(val, 0); + ppu.intLcdStat = isSet(val, 1); + timer.interrupt = isSet(val, 2); + _intSerial = isSet(val, 3); + joypad.interrupt = isSet(val, 4); + } + + void writeSerial(int val) {} + + void writeSerialCtrl(int val) { + final cur = isSet(val, 7); + + if (_prevSerial && !cur) { + _intSerial = true; + } + + _prevSerial = cur; + } + + void writeDma(int val) { + final baseAddr = (val << 8).toU16(); + + for (var i = 0; i < 0x100; i++) { + write(0xFE00 + i, read(baseAddr + i)); + } + } +} diff --git a/lib/emulator/cpu.dart b/lib/emulator/cpu.dart new file mode 100644 index 0000000..3715701 --- /dev/null +++ b/lib/emulator/cpu.dart @@ -0,0 +1,1657 @@ +import 'package:dartboy/emulator/bus.dart'; +import 'package:dartboy/emulator/utils.dart'; + +class _F extends BitFieldU8 { + _F() : super({}); + + _F.fromU8(int u8) : super.fromU8(u8); + + Bit get c => value(4); + Bit get h => value(5); + Bit get n => value(6); + Bit get z => value(7); +} + +class Cpu { + Cpu({ + required this.bus, + }); + + int _a = 0; + _F _f = _F(); + int _bc = 0; + int _de = 0; + int _hl = 0; + int _sp = 0; + int _pc = 0; + int _stalls = 0; + bool _ime = false; + bool _halted = false; + + int _left = -1; + + Bus bus; + + void reset() { + _a = 0x01; + _f = _F.fromU8(0xB0); + _bc = 0x0013; + _de = 0x00D8; + _hl = 0x014D; + _sp = 0xFFFE; + _pc = 0x0100; + _stalls = 0; + } + + void tick() { + if (_stalls > 0) { + _stalls -= 1; + + return; + } + + _stalls += 4; + + if (_ime && _interrupt()) { + _ime = false; + _halted = false; + } + + if (_halted) { + return; + } + + if (_left > 0) _left -= 1; + + if (_left == 0) { + return; + } + + final opecode = bus.read(_pc); + + _pc = _pc.wrappingAddU16(1); + + _doMnemonic(opecode); + } + + int get _b => (_bc & 0xFF00) >> 8; + + int get _c => _bc & 0x00FF; + + int get _d => (_de & 0xFF00) >> 8; + + int get _e => _de & 0x00FF; + + int get _h => (_hl & 0xFF00) >> 8; + + int get _l => _hl & 0x00FF; + + int get _af => (_a << 8) | _f.toU8(); + + set _b(int val) { + _bc &= 0x00FF; + _bc |= (val << 8).toU16(); + } + + set _c(int val) { + _bc &= 0xFF00; + _bc |= val; + } + + set _d(int val) { + _de &= 0x00FF; + _de |= (val << 8).toU16(); + } + + set _e(int val) { + _de &= 0xFF00; + _de |= val; + } + + set _h(int val) { + _hl &= 0x00FF; + _hl |= (val << 8).toU16(); + } + + set _l(int val) { + _hl &= 0xFF00; + _hl |= val; + } + + set _af(int val) { + _a = (val >> 8).toU8(); + _f = _F.fromU8(val & 0x00F0); + } + + int _r8(int index) { + switch (index) { + case 0: + return _b; + case 1: + return _c; + case 2: + return _d; + case 3: + return _e; + case 4: + return _h; + case 5: + return _l; + case 6: + return bus.read(_hl); + case 7: + return _a; + default: + throw ArgumentError.value(index); + } + } + + void _setR8(int index, int val) { + switch (index) { + case 0: + _b = val; + break; + case 1: + _c = val; + break; + case 2: + _d = val; + break; + case 3: + _e = val; + break; + case 4: + _h = val; + break; + case 5: + _l = val; + break; + case 6: + bus.write(_hl, val); + break; + case 7: + _a = val; + break; + default: + throw ArgumentError.value(index); + } + } + + int _r16(int index, bool high) { + switch (index) { + case 0: + return _bc; + case 1: + return _de; + case 2: + return _hl; + case 3: + if (high) { + return _af; + } else { + return _sp; + } + default: + throw ArgumentError.value(index); + } + } + + void _setR16(int index, int val, bool high) { + switch (index) { + case 0: + _bc = val; + break; + case 1: + _de = val; + break; + case 2: + _hl = val; + break; + case 3: + if (high) { + _af = val; + } else { + _sp = val; + } + break; + default: + throw ArgumentError.value(index); + } + } + + bool _carryPositive(int left, int right) { + return (left & 0xFF) + (right & 0xFF) > 0xFF; + } + + bool _carryNegative(int left, int right) { + return (left & 0xFF) < (right & 0xFF); + } + + bool _halfCarryPositive(int left, int right) { + return (left & 0x0F) + (right & 0x0F) > 0x0F; + } + + bool _halfCarryNegative(int left, int right) { + return (left & 0x0F) < (right & 0x0F); + } + + bool _carryPositiveU16(int left, int right) { + return (left & 0xFFFF) + (right & 0xFFFF) > 0xFFFF; + } + + bool _halfCarryPositiveU16U12(int left, int right) { + return (left & 0x0FFF) + (right & 0x0FFF) > 0x0FFF; + } + + bool _interrupt() { + int interrupt = 0x0040; + + if (bus.ie.vBlank.val && bus.irqVBlank) { + bus.irqVBlank = false; + + _call(interrupt); + + return true; + } + + interrupt += 0x0008; + + if (bus.ie.lcdStat.val && bus.irqLcdStat) { + bus.irqLcdStat = false; + + _call(interrupt); + + return true; + } + + interrupt += 0x0008; + + if (bus.ie.timer.val && bus.irqTimer) { + bus.irqTimer = false; + + _call(interrupt); + + return true; + } + + interrupt += 0x0008; + + if (bus.ie.serial.val && bus.irqSerial) { + bus.irqSerial = false; + + _call(interrupt); + + return true; + } + + interrupt += 0x0008; + + if (bus.ie.joypad.val && bus.irqJoypad) { + bus.irqJoypad = false; + + _call(interrupt); + + return true; + } + + return false; + } + + void _doMnemonic(int opecode) { + switch (opecode) { + case 0x00: + _nop(); + return; + case 0x76: + _halt(); + return; + case 0x10: + _stop(); + return; + case 0xF3: + _di(); + return; + case 0xFB: + _ei(); + return; + case 0x0A: + _loadU8AAddrBc(); + return; + case 0x1A: + _loadU8AAddrDe(); + return; + case 0x02: + _loadU8AddrBcA(); + return; + case 0x12: + _loadU8AddrDeA(); + return; + case 0xFA: + _loadU8AAddrIm16(); + return; + case 0xEA: + _loadU8AddrIm16A(); + return; + case 0xF2: + _loadU8AAddrIndexC(); + return; + case 0xE2: + _loadU8AddrIndexCA(); + return; + case 0xF0: + _loadU8AAddrIndexIm8(); + return; + case 0xE0: + _loadU8AddrIndexIm8A(); + return; + case 0x3A: + _loadDecU8AAddrHl(); + return; + case 0x32: + _loadDecU8AddrHlA(); + return; + case 0x2A: + _loadIncU8AAddrHl(); + return; + case 0x22: + _loadIncU8AddrHlA(); + return; + case 0x08: + _loadU16AddrIm16Sp(); + return; + case 0xF8: + _loadU16HlIndexIm8Sp(); + return; + case 0xF9: + _loadU16SpHl(); + return; + case 0xC6: + _addU8AIm8(); + return; + case 0xCE: + _addCarryU8AIm8(); + return; + case 0xD6: + _subU8AIm8(); + return; + case 0xDE: + _subCarryU8AIm8(); + return; + case 0xE6: + _andU8AIm8(); + return; + case 0xF6: + _orU8AIm8(); + return; + case 0xEE: + _xorU8AIm8(); + return; + case 0xFE: + _cpU8AIm8(); + return; + case 0xE8: + _addU16SpIm8(); + return; + case 0x07: + _rlcaU8(); + return; + case 0x17: + _rlaU8(); + return; + case 0x0F: + _rrcaU8(); + return; + case 0x1F: + _rraU8(); + return; + case 0x27: + _decimalAdjustU8A(); + return; + case 0x2F: + _complementU8A(); + return; + case 0x3F: + _complementCarry(); + return; + case 0x37: + _setCarryFlag(); + return; + case 0xC3: + _jpU16(); + return; + case 0xC2: + _jpU16Nz(); + return; + case 0xCA: + _jpU16Z(); + return; + case 0xD2: + _jpU16Nc(); + return; + case 0xDA: + _jpU16C(); + return; + case 0xE9: + _jpU16Hl(); + return; + case 0x18: + _jrU8ImU8(); + return; + case 0x20: + _jrU8Nz(); + return; + case 0x28: + _jrU8Z(); + return; + case 0x30: + _jrU8Nc(); + return; + case 0x38: + _jrU8C(); + return; + case 0xCD: + _callU16(); + return; + case 0xC4: + _callU16Nz(); + return; + case 0xCC: + _callU16Z(); + return; + case 0xD4: + _callU16Nc(); + return; + case 0xDC: + _callU16C(); + return; + case 0xC9: + _ret(); + return; + case 0xC0: + _retNz(); + return; + case 0xC8: + _retZ(); + return; + case 0xD0: + _retNc(); + return; + case 0xD8: + _retC(); + return; + case 0xD9: + _reti(); + return; + case 0xCB: + { + final prefixed = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + _doMnemonicPrefixed(prefixed); + return; + } + } + + if (0x40 <= opecode && opecode <= 0x7F) { + _loadU8RR(decodeX(opecode), decodeY(opecode)); + return; + } + + if (0x06 <= opecode && opecode <= 0x3E && (opecode & 7 == 6)) { + _loadU8RIm8(decodeX(opecode)); + return; + } + + if (0x01 <= opecode && opecode <= 0x31 && (opecode & 15 == 1)) { + _loadU16RrIm16(decodeR(opecode)); + return; + } + + if (0xC5 <= opecode && opecode <= 0xF5 && (opecode & 15 == 5)) { + _pushU16Rr(decodeR(opecode)); + return; + } + + if (0xC1 <= opecode && opecode <= 0xF1 && (opecode & 15 == 1)) { + _popU16Rr(decodeR(opecode)); + return; + } + + if (0x80 <= opecode && opecode <= 0x87) { + _addU8AR(decodeY(opecode)); + return; + } + + if (0x88 <= opecode && opecode <= 0x8f) { + _addCarryU8AR(decodeY(opecode)); + return; + } + + if (0x90 <= opecode && opecode <= 0x97) { + _subU8AR(decodeY(opecode)); + return; + } + + if (0x98 <= opecode && opecode <= 0x9F) { + _subCarryU8AR(decodeY(opecode)); + return; + } + + if (0xA0 <= opecode && opecode <= 0xA7) { + _andU8AR(decodeY(opecode)); + return; + } + + if (0xB0 <= opecode && opecode <= 0xB7) { + _orU8AR(decodeY(opecode)); + return; + } + + if (0xA8 <= opecode && opecode <= 0xAF) { + _xorU8AR(decodeY(opecode)); + return; + } + + if (0xB8 <= opecode && opecode <= 0xBF) { + _cpU8AR(decodeY(opecode)); + return; + } + + if (0x04 <= opecode && opecode <= 0x3C && (opecode & 7 == 4)) { + _incU8R(decodeX(opecode)); + return; + } + + if (0x05 <= opecode && opecode <= 0x3D && (opecode & 7 == 5)) { + _decU8R(decodeX(opecode)); + return; + } + + if (0x09 <= opecode && opecode <= 0x39 && (opecode & 15 == 9)) { + _addU16HlRr(decodeR(opecode)); + return; + } + + if (0x03 <= opecode && opecode <= 0x33 && (opecode & 15 == 3)) { + _incU16Rr(decodeR(opecode)); + return; + } + + if (0x0B <= opecode && opecode <= 0x3B && (opecode & 15 == 11)) { + _decU16Rr(decodeR(opecode)); + return; + } + + if (0xC7 <= opecode && (opecode & 7 == 7)) { + _restart(decodeX(opecode)); + return; + } + + throw ArgumentError.value(opecode); + } + + void _doMnemonicPrefixed(int opecode) { + if (0x30 <= opecode && opecode <= 0x37) { + _swapU8R(decodeY(opecode)); + return; + } + + if (0x00 <= opecode && opecode <= 0x07) { + _rlcU8R(decodeY(opecode)); + return; + } + + if (0x10 <= opecode && opecode <= 0x17) { + _rlU8R(decodeY(opecode)); + return; + } + + if (0x08 <= opecode && opecode <= 0x0F) { + _rrcU8R(decodeY(opecode)); + return; + } + + if (0x18 <= opecode && opecode <= 0x1F) { + _rrU8R(decodeY(opecode)); + return; + } + + if (0x20 <= opecode && opecode <= 0x27) { + _slaU8R(decodeY(opecode)); + return; + } + + if (0x28 <= opecode && opecode <= 0x2F) { + _sraU8R(decodeY(opecode)); + return; + } + + if (0x38 <= opecode && opecode <= 0x3F) { + _srlU8R(decodeY(opecode)); + return; + } + + if (0x40 <= opecode && opecode <= 0x7F) { + _bitU8BitR(decodeY(opecode), decodeX(opecode)); + return; + } + + if (0xC0 <= opecode && opecode <= 0xFF) { + _setU8BitR(decodeY(opecode), decodeX(opecode)); + return; + } + + if (0x80 <= opecode && opecode <= 0xBF) { + _resetU8BitR(decodeY(opecode), decodeX(opecode)); + return; + } + + throw ArgumentError.value(opecode); + } + + void _nop() {} + + void _halt() { + _halted = true; + } + + void _stop() {} + + void _di() { + _ime = false; + } + + void _ei() { + _ime = true; + } + + void _loadU8RIm8(int index) { + final val = bus.read(_pc); + + _pc = _pc.wrappingAddU16(1); + + _setR8(index, val); + } + + void _loadU8RR(int left, int right) { + final val = _r8(right); + _setR8(left, val); + } + + void _loadU8AAddrBc() { + final val = bus.read(_bc); + _a = val; + } + + void _loadU8AAddrDe() { + final val = bus.read(_de); + _a = val; + } + + void _loadU8AddrBcA() { + bus.write(_bc, _a); + } + + void _loadU8AddrDeA() { + bus.write(_de, _a); + } + + void _loadU8AAddrIm16() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + final val = bus.read(addr); + _a = val; + } + + void _loadU8AddrIm16A() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + final val = _a; + bus.write(addr, val); + } + + void _loadU8AAddrIndexC() { + final index = _c; + final addr = 0xFF00 + index; + final val = bus.read(addr); + _a = val; + } + + void _loadU8AddrIndexCA() { + final index = _c; + final addr = 0xFF00 + index; + bus.write(addr, _a); + } + + void _loadU8AAddrIndexIm8() { + final index = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final addr = 0xFF00 + index; + final val = bus.read(addr); + _a = val; + } + + void _loadU8AddrIndexIm8A() { + final index = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final addr = 0xFF00 + index; + bus.write(addr, _a); + } + + void _loadDecU8AAddrHl() { + final val = bus.read(_hl); + _hl = _hl.wrappingSubU16(1); + _a = val; + } + + void _loadDecU8AddrHlA() { + bus.write(_hl, _a); + _hl = _hl.wrappingSubU16(1); + } + + void _loadIncU8AAddrHl() { + final val = bus.read(_hl); + _hl = _hl.wrappingAddU16(1); + _a = val; + } + + void _loadIncU8AddrHlA() { + bus.write(_hl, _a); + _hl = _hl.wrappingAddU16(1); + } + + void _loadU16RrIm16(int index) { + final val = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + _setR16(index, val, false); + } + + void _loadU16AddrIm16Sp() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + final val = _sp; + bus.writeWord(addr, val); + } + + void _loadU16HlIndexIm8Sp() { + final baseAddr = _sp; + final indexAddr = bus.read(_pc).toI8(); + _pc = _pc.wrappingAddU16(1); + _hl = baseAddr.wrappingAddU16(indexAddr); + + _f.z.reset(); + _f.n.reset(); + _f.h.val = _halfCarryPositive(baseAddr.toU8(), indexAddr.toU8()); + _f.c.val = _carryPositive(baseAddr.toU8(), indexAddr.toU8()); + } + + void _loadU16SpHl() { + _sp = _hl; + + _stalls += 8; + } + + void _pushU16Rr(int index) { + final val = _r16(index, true); + _sp = _sp.wrappingSubU16(2); + bus.writeWord(_sp, val); + + _stalls += 16; + } + + void _popU16Rr(int index) { + final val = bus.readWord(_sp); + _sp = _sp.wrappingAddU16(2); + _setR16(index, val, true); + + _stalls += 12; + } + + void _addU8AR(int index) { + final left = _a; + final right = _r8(index); + final result = left.wrappingAddU8(right); + + _a = result; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.val = _halfCarryPositive(left, right); + _f.c.val = _carryPositive(left, right); + + _stalls += 4; + } + + void _addU8AIm8() { + final right = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final left = _a; + final result = left.wrappingAddU8(right); + + _a = result; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.val = _halfCarryPositive(left, right); + _f.c.val = _carryPositive(left, right); + + _stalls += 8; + } + + void _addCarryU8AR(int index) { + final c = _f.c.val ? 1 : 0; + final right = _r8(index); + final left = _a; + final result1 = left.wrappingAddU8(right); + final result2 = result1.wrappingAddU8(c); + + final c1 = _carryPositive(left, right); + final h1 = _halfCarryPositive(left, right); + final c2 = _carryPositive(result1, c); + final h2 = _halfCarryPositive(result1, c); + + _a = result2; + + _f.z.val = result2 == 0; + _f.n.reset(); + _f.h.val = h1 || h2; + _f.c.val = c1 || c2; + + _stalls += 4; + } + + void _addCarryU8AIm8() { + final c = _f.c.val ? 1 : 0; + final right = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final left = _a; + final result1 = left.wrappingAddU8(right); + final result2 = result1.wrappingAddU8(c); + + final c1 = _carryPositive(left, right); + final h1 = _halfCarryPositive(left, right); + final c2 = _carryPositive(result1, c); + final h2 = _halfCarryPositive(result1, c); + + _a = result2; + + _f.z.val = result2 == 0; + _f.n.reset(); + _f.h.val = h1 || h2; + _f.c.val = c1 || c2; + + _stalls += 8; + } + + void _subU8AR(int index) { + final left = _a; + final right = _r8(index); + final result = left.wrappingSubU8(right); + + _a = result; + + _f.z.val = result == 0; + _f.n.set(); + _f.h.val = _halfCarryNegative(left, right); + _f.c.val = _carryNegative(left, right); + + _stalls += 4; + } + + void _subU8AIm8() { + final left = _a; + final right = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final result = left.wrappingSubU8(right); + + _a = result; + + _f.z.val = result == 0; + _f.n.set(); + _f.h.val = _halfCarryNegative(left, right); + _f.c.val = _carryNegative(left, right); + + _stalls += 8; + } + + void _subCarryU8AR(int index) { + final c = _f.c.val ? 1 : 0; + final left = _a; + final right = _r8(index); + final result1 = left.wrappingSubU8(right); + final result2 = result1.wrappingSubU8(c); + + _a = result2; + + final c1 = _carryNegative(left, right); + final h1 = _halfCarryNegative(left, right); + final c2 = _carryNegative(result1, c); + final h2 = _halfCarryNegative(result1, c); + + _f.z.val = result2 == 0; + _f.n.set(); + _f.h.val = h1 || h2; + _f.c.val = c1 || c2; + + _stalls += 4; + } + + void _subCarryU8AIm8() { + final c = _f.c.val ? 1 : 0; + final left = _a; + final right = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final result1 = left.wrappingSubU8(right); + final result2 = result1.wrappingSubU8(c); + + _a = result2; + + final c1 = _carryNegative(left, right); + final h1 = _halfCarryNegative(left, right); + final c2 = _carryNegative(result1, c); + final h2 = _halfCarryNegative(result1, c); + + _f.z.val = result2 == 0; + _f.n.set(); + _f.h.val = h1 || h2; + _f.c.val = c1 || c2; + + _stalls += 8; + } + + void _andU8AR(int index) { + final left = _a; + final right = _r8(index); + final result = left & right; + + _a = result; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.set(); + _f.c.reset(); + + _stalls += 4; + } + + void _andU8AIm8() { + final left = _a; + final right = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final result = left & right; + + _a = result; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.set(); + _f.c.reset(); + + _stalls += 8; + } + + void _orU8AR(int index) { + final left = _a; + final right = _r8(index); + final result = left | right; + + _a = result; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.reset(); + + _stalls += 4; + } + + void _orU8AIm8() { + final left = _a; + final right = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final result = left | right; + + _a = result; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.reset(); + + _stalls += 8; + } + + void _xorU8AR(int index) { + final left = _a; + final right = _r8(index); + final result = left ^ right; + + _a = result; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.reset(); + + _stalls += 4; + } + + void _xorU8AIm8() { + final left = _a; + final right = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final result = left ^ right; + + _a = result; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.reset(); + + _stalls += 8; + } + + void _cpU8AR(int index) { + final left = _a; + final right = _r8(index); + final result = left.wrappingSubU8(right); + + _f.z.val = result == 0; + _f.n.set(); + _f.h.val = _halfCarryNegative(left, right); + _f.c.val = _carryNegative(left, right); + + _stalls += 4; + } + + void _cpU8AIm8() { + final left = _a; + final right = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + final result = left.wrappingSubU8(right); + + _f.z.val = result == 0; + _f.n.set(); + _f.h.val = _halfCarryNegative(left, right); + _f.c.val = _carryNegative(left, right); + + _stalls += 8; + } + + void _incU8R(int index) { + final left = _r8(index); + const right = 1; + final result = left.wrappingAddU8(right); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.val = _halfCarryPositive(left, right); + + _stalls += 4; + } + + void _decU8R(int index) { + final left = _r8(index); + const right = 1; + final result = left.wrappingSubU8(right); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.set(); + _f.h.val = _halfCarryNegative(left, right); + + _stalls += 4; + } + + void _addU16HlRr(int index) { + final left = _hl; + final right = _r16(index, false); + final result = left.wrappingAddU16(right); + + _hl = result; + + _f.n.reset(); + _f.h.val = _halfCarryPositiveU16U12(left, right); + _f.c.val = _carryPositiveU16(left, right); + + _stalls += 8; + } + + void _addU16SpIm8() { + final left = _sp; + final right = bus.read(_pc).toI8(); + _pc = _pc.wrappingAddU16(1); + final result = left.wrappingAddU16(right); + + _sp = result; + + _f.z.reset(); + _f.n.reset(); + _f.h.val = _halfCarryPositive(left.toU8(), right.toU8()); + _f.c.val = _carryPositive(left.toU8(), right.toU8()); + + _stalls += 16; + } + + void _incU16Rr(int index) { + final left = _r16(index, false); + const right = 1; + final result = left.wrappingAddU16(right); + + _setR16(index, result, false); + + _stalls += 8; + } + + void _decU16Rr(int index) { + final left = _r16(index, false); + const right = 1; + final result = left.wrappingSubU16(right); + + _setR16(index, result, false); + + _stalls += 8; + } + + void _rlcaU8() { + final val = _a; + final c = (val >> 7) & 1; + final result = val.rotateLeftU8(); + + _a = result; + + _f.z.reset(); + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 4; + } + + void _rlaU8() { + final val = _a; + final c = (val >> 7) & 1; + final result = (val << 1).toU8() | (_f.c.val ? 1 : 0); + + _a = result; + + _f.z.reset(); + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 4; + } + + void _rrcaU8() { + final val = _a; + final c = val & 1; + final result = val.rotateRightU8(); + + _a = result; + + _f.z.reset(); + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 4; + } + + void _rraU8() { + final val = _a; + final c = val & 1; + final result = (val >> 1).toU8() | ((_f.c.val ? 1 : 0) << 7); + + _a = result; + + _f.z.reset(); + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 4; + } + + void _rlcU8R(int index) { + final val = _r8(index); + final c = (val >> 7) & 1; + final result = val.rotateLeftU8(); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 8; + } + + void _rlU8R(int index) { + final val = _r8(index); + final c = (val >> 7) & 1; + final result = (val << 1).toU8() | (_f.c.val ? 1 : 0); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 8; + } + + void _rrcU8R(int index) { + final val = _r8(index); + final c = val & 1; + final result = val.rotateRightU8(); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 8; + } + + void _rrU8R(int index) { + final val = _r8(index); + final c = val & 1; + final result = (val >> 1).toU8() | ((_f.c.val ? 1 : 0) << 7); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 8; + } + + void _slaU8R(int index) { + final val = _r8(index); + final c = (val >> 7) & 1; + final result = (val << 1).toU8(); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 8; + } + + void _sraU8R(int index) { + final val = _r8(index); + final c = val & 1; + final result = (val >> 1).toU8() | oneHot(isSet(val, 7), 7); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 8; + } + + void _srlU8R(int index) { + final val = _r8(index); + final c = val & 1; + final result = (val >> 1).toU8(); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.val = c == 1; + + _stalls += 8; + } + + void _bitU8BitR(int index, int bit) { + final left = _r8(index); + final right = bit; + final result = (left >> right) & 1; + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.set(); + + _stalls += 8; + } + + void _setU8BitR(int index, int bit) { + final left = _r8(index); + final right = bit; + final result = left | (1 << right); + + _setR8(index, result); + + _stalls += 8; + } + + void _resetU8BitR(int index, int bit) { + final left = _r8(index); + final right = bit; + final result = left & ~(1 << right); + + _setR8(index, result); + + _stalls += 8; + } + + void _jpU16() { + final addr = bus.readWord(_pc); + + _pc = addr; + + _stalls += 16; + } + + void _jpU16Nz() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + if (!_f.z.val) { + _pc = addr; + } + + _stalls += 16; + } + + void _jpU16Z() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + if (_f.z.val) { + _pc = addr; + } + + _stalls += 16; + } + + void _jpU16Nc() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + if (!_f.c.val) { + _pc = addr; + } + + _stalls += 16; + } + + void _jpU16C() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + if (_f.c.val) { + _pc = addr; + } + + _stalls += 16; + } + + void _jpU16Hl() { + _pc = _hl; + + _stalls += 4; + } + + void _jrU8ImU8() { + final index = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + _pc = _pc.wrappingAddU16(index.toI8()); + + _stalls += 12; + } + + void _jrU8Nz() { + final index = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + + if (!_f.z.val) { + _pc = _pc.wrappingAddU16(index.toI8()); + } + + _stalls += 12; + } + + void _jrU8Z() { + final index = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + + if (_f.z.val) { + _pc = _pc.wrappingAddU16(index.toI8()); + } + + _stalls += 12; + } + + void _jrU8Nc() { + final index = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + + if (!_f.c.val) { + _pc = _pc.wrappingAddU16(index.toI8()); + } + + _stalls += 12; + } + + void _jrU8C() { + final index = bus.read(_pc); + _pc = _pc.wrappingAddU16(1); + + if (_f.c.val) { + _pc = _pc.wrappingAddU16(index.toI8()); + } + + _stalls += 12; + } + + void _call(int addr) { + _sp = _sp.wrappingSubU16(2); + bus.writeWord(_sp, _pc); + _pc = addr; + + _stalls += 24; + } + + void _callU16() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + _call(addr); + } + + void _callU16Nz() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + if (!_f.z.val) { + _call(addr); + } + } + + void _callU16Z() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + if (_f.z.val) { + _call(addr); + } + } + + void _callU16Nc() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + if (!_f.c.val) { + _call(addr); + } + } + + void _callU16C() { + final addr = bus.readWord(_pc); + _pc = _pc.wrappingAddU16(2); + + if (_f.c.val) { + _call(addr); + } + } + + void _restart(int param) { + final addr = param * 0x08; + _sp = _sp.wrappingSubU16(2); + bus.writeWord(_sp, _pc); + _pc = addr; + + _stalls += 16; + } + + void _ret() { + final addr = bus.readWord(_sp); + _sp = _sp.wrappingAddU16(2); + _pc = addr; + + _stalls += 16; + } + + void _retNz() { + final addr = bus.readWord(_sp); + + if (!_f.z.val) { + _sp = _sp.wrappingAddU16(2); + _pc = addr; + } + + _stalls += 20; + } + + void _retZ() { + final addr = bus.readWord(_sp); + + if (_f.z.val) { + _sp = _sp.wrappingAddU16(2); + _pc = addr; + } + + _stalls += 20; + } + + void _retNc() { + final addr = bus.readWord(_sp); + + if (!_f.c.val) { + _sp = _sp.wrappingAddU16(2); + _pc = addr; + } + + _stalls += 20; + } + + void _retC() { + final addr = bus.readWord(_sp); + + if (_f.c.val) { + _sp = _sp.wrappingAddU16(2); + _pc = addr; + } + + _stalls += 20; + } + + void _reti() { + final addr = bus.readWord(_sp); + _sp = _sp.wrappingAddU16(2); + _pc = addr; + + _ime = true; + + _stalls += 16; + } + + void _swapU8R(int index) { + final val = _r8(index); + final high = val & 0xF0; + final low = val & 0x0F; + final result = (high >> 4) | (low << 4); + + _setR8(index, result); + + _f.z.val = result == 0; + _f.n.reset(); + _f.h.reset(); + _f.c.reset(); + + _stalls += 8; + } + + void _decimalAdjustU8A() { + if (!_f.n.val) { + if (_f.c.val || _a > 0x99) { + _a = _a.wrappingAddU8(0x60); + _f.c.set(); + } + if (_f.h.val || (_a & 0x0F) > 0x09) { + _a = _a.wrappingAddU8(0x06); + } + } else { + if (_f.c.val) { + _a = _a.wrappingSubU8(0x60); + } + if (_f.h.val) { + _a = _a.wrappingSubU8(0x06); + } + } + + _f.z.val = _a == 0; + _f.h.reset(); + + _stalls += 4; + } + + void _complementU8A() { + final val = _a; + final result = (~val).toU8(); + + _a = result; + _f.n.set(); + _f.h.set(); + + _stalls += 4; + } + + void _complementCarry() { + final c = _f.c.val; + final result = !c; + + _f.n.reset(); + _f.h.reset(); + _f.c.val = result; + + _stalls += 4; + } + + void _setCarryFlag() { + _f.n.reset(); + _f.h.reset(); + _f.c.set(); + + _stalls += 4; + } +} diff --git a/lib/emulator/gameboy.dart b/lib/emulator/gameboy.dart new file mode 100644 index 0000000..15fbccc --- /dev/null +++ b/lib/emulator/gameboy.dart @@ -0,0 +1,46 @@ +import 'dart:typed_data'; + +import 'package:dartboy/emulator/bus.dart'; +import 'package:dartboy/emulator/cpu.dart'; +import 'package:dartboy/emulator/joypad.dart'; +import 'package:dartboy/emulator/mbc.dart'; +import 'package:dartboy/emulator/ppu.dart'; +import 'package:dartboy/emulator/rom.dart'; + +class GameBoy { + GameBoy(); + + late Cpu cpu; + bool ready = false; + + void load(Rom rom) { + final mbc = Mbc.fromRom(rom); + final ppu = Ppu(); + final bus = Bus(ppu, mbc); + + cpu = Cpu(bus: bus); + } + + void reset() { + cpu.reset(); + + ready = true; + } + + void press(JoypadKey key) { + cpu.bus.joypad.press(key); + } + + void release(JoypadKey key) { + cpu.bus.joypad.release(key); + } + + void tick() { + cpu.tick(); + cpu.bus.tick(); + } + + Uint8List render() { + return cpu.bus.ppu.render(); + } +} diff --git a/lib/emulator/joypad.dart b/lib/emulator/joypad.dart new file mode 100644 index 0000000..a6d0440 --- /dev/null +++ b/lib/emulator/joypad.dart @@ -0,0 +1,117 @@ +import 'package:dartboy/emulator/utils.dart'; + +enum JoypadKey { + a, + b, + up, + down, + left, + right, + select, + start, +} + +class Joypad { + Joypad(); + + bool _up = false; + bool _down = false; + bool _left = false; + bool _right = false; + bool _a = false; + bool _b = false; + bool _start = false; + bool _select = false; + + bool _direction = false; + bool _button = false; + + bool interrupt = false; + + void press(JoypadKey key) { + switch (key) { + case JoypadKey.a: + _a = true; + break; + case JoypadKey.b: + _b = true; + break; + case JoypadKey.select: + _select = true; + break; + case JoypadKey.start: + _start = true; + break; + case JoypadKey.up: + _up = true; + break; + case JoypadKey.down: + _down = true; + break; + case JoypadKey.right: + _right = true; + break; + case JoypadKey.left: + _left = true; + break; + } + + interrupt = true; + } + + void release(JoypadKey key) { + switch (key) { + case JoypadKey.a: + _a = false; + break; + case JoypadKey.b: + _b = false; + break; + case JoypadKey.select: + _select = false; + break; + case JoypadKey.start: + _start = false; + break; + case JoypadKey.up: + _up = false; + break; + case JoypadKey.down: + _down = false; + break; + case JoypadKey.right: + _right = false; + break; + case JoypadKey.left: + _left = false; + break; + } + } + + int readButton() { + return bitpack( + [true, true, false, !_direction, !_start, !_select, !_b, !_a]); + } + + int readDirection() { + return bitpack( + [true, true, !_button, false, !_down, !_up, !_left, !_right]); + } + + int read() { + if (_direction) { + return readDirection(); + } + + if (_button) { + return readButton(); + } + + return 0xFF; + } + + void write(int val) { + _direction = !isSet(val, 4); + _button = !isSet(val, 5); + } +} diff --git a/lib/emulator/mbc.dart b/lib/emulator/mbc.dart new file mode 100644 index 0000000..0e7968d --- /dev/null +++ b/lib/emulator/mbc.dart @@ -0,0 +1,149 @@ +import 'dart:math'; + +import 'package:dartboy/emulator/rom.dart'; + +abstract class Mbc { + int read(int addr); + void write(int addr, int val); + + factory Mbc.fromRom(Rom rom) { + switch (rom.mbcType) { + case MbcType.romOnly: + return RomOnly(rom); + case MbcType.mbc1: + case MbcType.mbc1Ram: + case MbcType.mbc1RamBattery: + return Mbc1(rom); + + default: + throw ArgumentError.value(rom.mbcType); + } + } +} + +class RomOnly implements Mbc { + RomOnly(this._rom); + + final Rom _rom; + final List _ram = List.filled(8 * 1024, 0, growable: false); + + @override + int read(int addr) { + if (addr >= 0xA000) { + return _ram[addr - 0xA000]; + } + + return _rom.data[addr]; + } + + @override + void write(int addr, int val) { + if (addr >= 0xA000) { + _ram[addr - 0xA000] = val; + } + + return; + } +} + +enum Mbc1SelectMode { + rom, + ram, +} + +class Mbc1 implements Mbc { + Mbc1(this._rom); + + final Rom _rom; + final List _ram = List.filled(32 * 1024, 0, growable: false); + int _romBank = 1; + int _ramBank = 0; + + bool _enableRam = true; + Mbc1SelectMode _selectMode = Mbc1SelectMode.rom; + + int _readRomFromBank(int addr) { + final baseAddr = _romBank * 16 * 1024; + final indexAddr = addr - 0x4000; + return _rom.data[baseAddr + indexAddr]; + } + + int _readRamFromBank(int addr) { + if (!_enableRam) { + print("disabled ram read"); + return 0; + } + + final baseAddr = _ramBank * 8 * 1024; + final indexAddr = addr - 0xA000; + return _ram[baseAddr + indexAddr]; + } + + void _writeRamIntoBank(int addr, int val) { + if (!_enableRam) { + print("disabled ram write"); + return; + } + + final baseAddr = _ramBank * 8 * 1024; + final indexAddr = addr - 0xA000; + + _ram[baseAddr + indexAddr] = val; + } + + @override + int read(int addr) { + if (0x0000 <= addr && addr <= 0x3FFF) return _rom.data[addr]; + if (0x4000 <= addr && addr <= 0x7FFF) return _readRomFromBank(addr); + if (0xA000 <= addr && addr <= 0xBFFF) return _readRamFromBank(addr); + + return 0; + } + + @override + void write(int addr, int val) { + if (0x0000 <= addr && addr <= 0x1FFF) { + if ((val & 0x0F) == 0x0A) { + _enableRam = true; + } else { + _enableRam = false; + } + return; + } + + if (0x2000 <= addr && addr <= 0x3FFF) { + final bank = val & 0x1F; + + _romBank = max(bank, 1); + return; + } + + if (0x4000 <= addr && addr <= 0x5FFF) { + switch (_selectMode) { + case Mbc1SelectMode.rom: + final bankHigh = val & 0x03; + + _romBank |= bankHigh << 5; + break; + + case Mbc1SelectMode.ram: + final bank = val & 0x03; + + _ramBank = bank; + break; + } + return; + } + + if (0x6000 <= addr && addr <= 0x7FFF) { + if (val == 0x01) { + _selectMode = Mbc1SelectMode.ram; + } else { + _selectMode = Mbc1SelectMode.rom; + } + return; + } + + _writeRamIntoBank(addr, val); + } +} diff --git a/lib/emulator/ppu.dart b/lib/emulator/ppu.dart new file mode 100644 index 0000000..7a5d2ab --- /dev/null +++ b/lib/emulator/ppu.dart @@ -0,0 +1,566 @@ +import 'dart:typed_data'; + +import 'package:dartboy/emulator/utils.dart'; + +const _width = 256; +const _height = 256; + +class _LcdControl extends BitFieldU8 { + _LcdControl() : super({}); + + _LcdControl.fromU8(int u8) : super.fromU8(u8); + + Bit get bgWinEnable => value(0); + Bit get spriteEnable => value(1); + Bit get spriteSize => value(2); + Bit get bgTileMapSelect => value(3); + Bit get tileDataSelect => value(4); + Bit get windowDisplayEnable => value(5); + Bit get windowTileMapSelect => value(6); + Bit get lcdDisplayEnable => value(7); +} + +class _LcdStatus extends BitFieldU8 { + _LcdStatus() : super({}); + + _LcdStatus.fromU8(int u8) : super.fromU8(u8); + + Bit get ppuMode0 => value(0); + Bit get ppuMode1 => value(1); + Bit get coincidenceFlag => value(2); + Bit get mode0StatIntEnable => value(3); + Bit get mode1StatIntEnable => value(4); + Bit get mode2StatIntEnable => value(5); + Bit get lycLyStatIntEnable => value(6); +} + +class _SpriteFlags extends BitFieldU8 { + _SpriteFlags() : super({}); + + _SpriteFlags.fromU8(int u8) : super.fromU8(u8); + + Bit get paletteNum => value(4); + Bit get xFlip => value(5); + Bit get yFlip => value(6); + Bit get priority => value(7); +} + +class _Palette { + _Palette(this.values); + + List values; + + _Palette.fromU8(int u8) + : values = List.generate(4, (i) => (u8 >> (2 * i)) & 3); + + int toU8() => values.fold(0, (acc, col) => (acc << 2) | col); +} + +class _Oam { + int yPos = 0; + int xPos = 0; + int tileNum = 0; + _SpriteFlags spriteFlag = _SpriteFlags(); +} + +enum _Mode { + hBlank, + vBlank, + oamScan, + drawing, +} + +class _OamColor { + _OamColor({ + this.index = 0, + this.color = 0, + this.blend = false, + }); + + final int index; + final int color; + final bool blend; + + static List<_OamColor> fromColorIndexes( + List indexes, bool blend, _Palette palette) { + return List.generate(8, (i) { + final index = indexes[i]; + return _OamColor( + index: index, + blend: blend, + color: palette.values[index], + ); + }); + } +} + +class Ppu { + Ppu(); + + final List _vram = List.filled(8 * 1024, 0); + + _Mode _mode = _Mode.oamScan; + _Mode _prevMode = _Mode.vBlank; + + _LcdControl _lcdControl = _LcdControl(); + _LcdStatus _lcdStatus = _LcdStatus(); + int _windowX = 0; + int _windowY = 0; + int _scrollX = 0; + int _scrollY = 0; + + int _cycles = 0; + int _lines = 0; + + int _linesCompare = 0; + + _Palette _bgPalette = _Palette.fromU8(0x00); + _Palette _objectPalette0 = _Palette.fromU8(0x00); + _Palette _objectPalette1 = _Palette.fromU8(0x00); + + bool intVBlank = false; + bool intLcdStat = false; + + int _x = 0; + int _y = 0; + + final List<_Oam> _oam = List.generate(0xA0, (_) => _Oam()); + final List<_Oam> _buffer = []; + + List _bgLine = List.filled(_width, 0); + List<_OamColor> _oamLine = List.generate(_width, (_) => _OamColor()); + List _curBg = List.filled(8, 0); + bool _drawingWindow = false; + + Uint8List pixels = Uint8List(4 * _width * _height); + + static const _colorToPixel = [ + [0xEF, 0xFD, 0xB4, 0xFF], + [0x8C, 0xAE, 0x04, 0xFF], + [0x65, 0x7F, 0x05, 0xFF], + [0x14, 0x19, 0x01, 0xFF], + ]; + + List _tileToIndexes(int tileNum, int row, bool signed) { + var baseAddr = 0x0000; + if (signed) { + baseAddr = 0x9000 - 0x8000; + } + + var indexAddr = row * 2 + tileNum * 16; + if (signed) { + indexAddr = row * 2 + tileNum.toI8() * 16; + } + + final addr = baseAddr.wrappingAddU16(indexAddr); + + var bit = _vram[addr]; + var color = _vram[addr + 1]; + + List indexes = List.filled(8, 0); + + for (var i = 7; i >= 0; i--) { + final index = ((bit & 1) << 1) | (color & 1); + indexes[i] = index; + + bit >>= 1; + color >>= 1; + } + + return indexes; + } + + List _tileMapToColors(int tileX, int tileY, int row, bool high) { + var baseAddr = 0x9800 - 0x8000; + if (high) { + baseAddr = 0x9C00 - 0x8000; + } + + final indexAddr = tileX + tileY * 32; + + final addr = baseAddr.wrappingAddU16(indexAddr); + + final tileNum = _vram[addr]; + + return _tileToIndexes(tileNum, row, !_lcdControl.tileDataSelect.val); + } + + List<_OamColor> _oamToColors(_Oam oam) { + var row = _y + 16 - oam.yPos; + var tile = oam.tileNum; + + if (oam.spriteFlag.yFlip.val) { + var limit = 8; + + if (_lcdControl.spriteSize.val) { + limit = 16; + } + + row = limit - row - 1; + } + + if (row >= 8) { + row -= 8; + tile += 1; + } + + var palette = _objectPalette0; + + if (oam.spriteFlag.paletteNum.val) { + palette = _objectPalette1; + } + + final blend = oam.spriteFlag.priority; + + var colors = _OamColor.fromColorIndexes( + _tileToIndexes(tile, row, false), blend.val, palette); + + if (oam.spriteFlag.xFlip.val) { + colors = colors.reversed.toList(); + } + + return colors; + } + + void _scanOam(int i) { + var size = 8; + if (_lcdControl.spriteSize.val) { + size = 16; + } + + final o = _oam[i]; + final curY = _lines + 16; + final targetY = o.yPos; + + if (o.xPos > 8 && + curY < targetY + size && + targetY <= curY && + _buffer.length < 10) { + _buffer.add(o); + } + } + + void _drawBg() { + if (_drawingWindow) { + return; + } + + final cx = _x.wrappingAddU8(_scrollX); + final cy = _y.wrappingAddU8(_scrollY); + final col = cx % 8; + final row = cy % 8; + final tileX = cx ~/ 8; + final tileY = cy ~/ 8; + + if (col == 0 || _x == 0) { + _curBg = + _tileMapToColors(tileX, tileY, row, _lcdControl.bgTileMapSelect.val); + } + + _bgLine[_x] = _curBg[col]; + } + + void _drawWindow() { + if (!_drawingWindow && !(_x + 7 == _windowX && _y >= _windowY)) { + return; + } + + _drawingWindow = true; + + final cx = _x.wrappingSubU8(_windowX); + final cy = _y.wrappingSubU8(_windowY); + final col = cx % 8; + final row = cy % 8; + final tileX = cx ~/ 8; + final tileY = cy ~/ 8; + + if (col == 0 || _x == 0) { + _curBg = _tileMapToColors( + tileX, + tileY, + row, + _lcdControl.windowTileMapSelect.val, + ); + } + _bgLine[_x] = _curBg[col]; + } + + void _drawSprite() { + for (final oam in _buffer) { + if (oam.xPos == _x + 8) { + final colors = _oamToColors(oam); + + for (var i = 0; i < 8; i++) { + _oamLine[_x + i] = colors[i]; + } + } + } + } + + void _putPixels(int x) { + final index = _bgLine[x]; + var color = _bgPalette.values[index]; + + final oam = _oamLine[x]; + + if ((!oam.blend || index == 0) && oam.index != 0) { + color = oam.color; + } + + final pixel = _colorToPixel[color]; + for (var i = 0; i < 4; i++) { + pixels[(x + _y * _width) * 4 + i] = pixel[i]; + } + } + + void tick() { + _cycles += 1; + + if (_cycles >= 456) { + _cycles = 0; + _lines += 1; + _buffer.clear(); + _bgLine = List.filled(_width, 0); + _oamLine = List.generate(_width, (_) => _OamColor()); + } + + if (_lines >= 154) { + _lines = 0; + } + + if (_cycles == 80) { + _x = 0; + } + + if (_lines == 0) { + _y = 0; + } + + if (_lines < 144) { + _y = _lines; + + if (0 <= _cycles && _cycles <= 79) { + _mode = _Mode.oamScan; + } + + if (_cycles == 80) { + _mode = _Mode.drawing; + } + + if (81 <= _cycles && _cycles <= 239) { + _x += 1; + } + + if (240 <= _cycles && _cycles <= 455) { + _mode = _Mode.hBlank; + } + } + + if (_lines == 144) { + _mode = _Mode.vBlank; + } + + switch (_mode) { + case _Mode.drawing: + if (_prevMode != _mode) { + _lcdStatus.ppuMode0.set(); + _lcdStatus.ppuMode1.set(); + } + + if (_lcdControl.bgWinEnable.val) { + if (_lcdControl.windowDisplayEnable.val) { + _drawWindow(); + } + + _drawBg(); + } + + if (_lcdControl.spriteEnable.val) { + _drawSprite(); + } + break; + case _Mode.hBlank: + if (_prevMode != _mode) { + _lcdStatus.ppuMode0.reset(); + _lcdStatus.ppuMode1.reset(); + + intLcdStat |= _lcdStatus.mode0StatIntEnable.val; + + _lcdStatus.coincidenceFlag.val = _lines == _linesCompare; + + intLcdStat |= _lcdStatus.lycLyStatIntEnable.val && + _lcdStatus.coincidenceFlag.val; + + _drawingWindow = false; + } + + if (_cycles < 400) { + _putPixels(_cycles - 240); + } + break; + case _Mode.oamScan: + if (_prevMode != _mode) { + _lcdStatus.ppuMode0.reset(); + _lcdStatus.ppuMode1.set(); + + intLcdStat |= _lcdStatus.mode2StatIntEnable.val; + } + + if (_cycles % 2 == 0) { + _scanOam(_cycles ~/ 2); + } + break; + case _Mode.vBlank: + if (_prevMode != _mode) { + _lcdStatus.ppuMode0.set(); + _lcdStatus.ppuMode1.reset(); + + intVBlank = true; + + intLcdStat |= _lcdStatus.mode1StatIntEnable.val; + } + + break; + default: + } + + _prevMode = _mode; + } + + int read(int addr) { + return _vram[addr - 0x8000]; + } + + void write(int addr, int val) { + _vram[addr - 0x8000] = val; + } + + int readOam(int addr) { + final indexAddr = addr - 0xFE00; + final index = indexAddr ~/ 4; + final offset = indexAddr % 4; + final o = _oam[index]; + + switch (offset) { + case 0: + return o.yPos; + case 1: + return o.xPos; + case 2: + return o.tileNum; + case 3: + return o.spriteFlag.toU8(); + default: + return 0; + } + } + + void writeOam(int addr, int val) { + final indexAddr = addr - 0xFE00; + final index = indexAddr ~/ 4; + final offset = indexAddr % 4; + + switch (offset) { + case 0: + _oam[index].yPos = val; + break; + case 1: + _oam[index].xPos = val; + break; + case 2: + _oam[index].tileNum = val; + break; + case 3: + _oam[index].spriteFlag = _SpriteFlags.fromU8(val); + break; + } + } + + int readLcdControl() { + return _lcdControl.toU8(); + } + + void writeLcdControl(int val) { + _lcdControl = _LcdControl.fromU8(val); + } + + int readLcdStatus() { + return _lcdStatus.toU8(); + } + + void writeLcdStatus(int val) { + _lcdStatus = _LcdStatus.fromU8(val); + } + + int readScrollY() { + return _scrollY; + } + + void writeScrollY(int val) { + _scrollY = val; + } + + int readScrollX() { + return _scrollX; + } + + void writeScrollX(int val) { + _scrollX = val; + } + + int readLines() { + return _lines; + } + + int readLineCompare() { + return _linesCompare; + } + + void writeLineCompare(int val) { + _linesCompare = val; + } + + int readWindowX() { + return _windowX; + } + + void writeWindowX(int val) { + _windowX = val; + } + + int readWindowY() { + return _windowY; + } + + void writeWindowY(int val) { + _windowY = val; + } + + int readBgPalette() { + return _bgPalette.toU8(); + } + + void writeBgPalette(int val) { + _bgPalette = _Palette.fromU8(val); + } + + int readObjectPalette0() { + return _objectPalette0.toU8(); + } + + void writeObjectPalette0(int val) { + _objectPalette0 = _Palette.fromU8(val); + } + + int readObjectPalette1() { + return _objectPalette1.toU8(); + } + + void writeObjectPalette1(int val) { + _objectPalette1 = _Palette.fromU8(val); + } + + Uint8List render() { + return Uint8List.fromList(pixels.toList()); + } +} diff --git a/lib/emulator/rom.dart b/lib/emulator/rom.dart new file mode 100644 index 0000000..aa7f273 --- /dev/null +++ b/lib/emulator/rom.dart @@ -0,0 +1,135 @@ +import 'package:dartboy/emulator/utils.dart'; + +enum MbcType { + romOnly, + mbc1, + mbc1Ram, + mbc1RamBattery, + mbc2, + mbc2Battery, + romRam, + romRamBattery, + mmm01, + mmm01Ram, + mmm01RamBattery, + mbc3, + mbc3Ram, + mbc3RamBattery, +} + +extension MbcTypeExt on MbcType { + static MbcType fromU8(int u8) { + switch (u8) { + case 0x00: + return MbcType.romOnly; + case 0x01: + return MbcType.mbc1; + case 0x02: + return MbcType.mbc1Ram; + case 0x03: + return MbcType.mbc1RamBattery; + case 0x05: + return MbcType.mbc2; + case 0x06: + return MbcType.mbc2Battery; + case 0x08: + return MbcType.romRam; + case 0x09: + return MbcType.romRamBattery; + case 0x0b: + return MbcType.mmm01; + case 0x0c: + return MbcType.mmm01Ram; + case 0x0d: + return MbcType.mmm01RamBattery; + case 0x11: + return MbcType.mbc3; + case 0x12: + return MbcType.mbc3Ram; + case 0x13: + return MbcType.mbc3RamBattery; + default: + throw ArgumentError.value(u8); + } + } +} + +class Rom { + late MbcType mbcType; + late int romSize; + late int ramSize; + late int headerChecksum; + late List data; + + Rom(this.data) { + mbcType = MbcTypeExt.fromU8(data[0x0147]); + + final size = data[0x0148]; + switch (size) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + romSize = (32 * 1024) << size; + break; + case 0x52: + romSize = (1.1 * 1024 * 1024).floor(); + break; + + case 0x53: + romSize = (1.2 * 1024 * 1024).floor(); + break; + + case 0x54: + romSize = (1.5 * 1024 * 1024).floor(); + break; + + default: + throw ArgumentError.value(size); + } + + switch (data[0x0149]) { + case 0x00: + ramSize = 0; + break; + case 0x01: + ramSize = 2 * 1024 * 1024; + break; + case 0x02: + ramSize = 8 * 1024 * 1024; + break; + case 0x03: + ramSize = 32 * 1024 * 1024; + break; + case 0x04: + ramSize = 128 * 1024 * 1024; + break; + case 0x05: + ramSize = 64 * 1024 * 1024; + break; + default: + throw ArgumentError.value(data[0x0149]); + } + + headerChecksum = data[0x014D]; + + var chksum = 0; + + for (var i = 0x0134; i <= 0x014C; i++) { + chksum = chksum.wrappingSubU8(data[i]).wrappingSubU8(1); + } + + if (headerChecksum != chksum) { + throw StateError('header checksum mismatch'); + } + + if (romSize != data.length) { + throw StateError('rom size mismatch'); + } + } +} diff --git a/lib/emulator/timer.dart b/lib/emulator/timer.dart new file mode 100644 index 0000000..5a06f36 --- /dev/null +++ b/lib/emulator/timer.dart @@ -0,0 +1,106 @@ +import 'package:dartboy/emulator/utils.dart'; + +enum _Clock { + clock4096, + clock262144, + clock65536, + clock16384, +} + +class Timer { + int _counter = 0; + int _tima = 0; + int _tma = 0; + bool _enable = false; + _Clock _clock = _Clock.clock4096; + bool _prev = false; + + bool interrupt = false; + + void _sync() { + var cur = false; + + if (_enable) { + int mask; + switch (_clock) { + case _Clock.clock4096: + mask = 1 << 9; + break; + + case _Clock.clock262144: + mask = 1 << 3; + break; + + case _Clock.clock65536: + mask = 1 << 5; + break; + + case _Clock.clock16384: + mask = 1 << 7; + break; + } + + cur = _counter & mask > 0; + } + + if (_prev && !cur) { + _tima = _tima.wrappingAddU8(1); + + if (_counter % 4 == 0 && _tima == 0) { + _tima = _tma; + interrupt = true; + } + } + + _prev = cur; + } + + void tick() { + _counter = _counter.wrappingAddU16(1); + + _sync(); + } + + int readDiv() => (_counter >> 8).toU8(); + + void writeDiv(int _) { + _counter = 0; + } + + int readTima() => _tima; + + void writeTima(int val) { + _sync(); + + _tima = val; + } + + int readTma() => _tma; + + void writeTma(int val) { + _tma = val; + + _sync(); + } + + int readTac() { + return bitpack([ + false, + false, + false, + false, + false, + _enable, + isSet(_clock.index, 1), + isSet(_clock.index, 0) + ]); + } + + void writeTac(int val) { + _enable = isSet(val, 2); + + final index = val & 3; + + _clock = _Clock.values[index]; + } +} diff --git a/lib/emulator/utils.dart b/lib/emulator/utils.dart new file mode 100644 index 0000000..633e066 --- /dev/null +++ b/lib/emulator/utils.dart @@ -0,0 +1,102 @@ +bool isSet(int val, int offset) { + return val & (1 << offset) > 0; +} + +int oneHot(bool? v, int offset) { + return ((v ?? false) ? 1 : 0) << offset; +} + +int bitpack(List list) { + return List.generate(8, (i) => oneHot(list[i], 7 - i)) + .fold(0, (acc, v) => acc | v); +} + +int decodeX(int opecode) { + return (opecode >> 3) & 7; +} + +int decodeY(int opecode) { + return opecode & 7; +} + +int decodeR(int opecode) { + return (opecode >> 4) & 3; +} + +class Bit { + Bit(this.val); + + factory Bit.fromU8(int u8, int index) => Bit(isSet(u8, index)); + + bool val; + + void set() { + val = true; + } + + void reset() { + val = false; + } +} + +class BitFieldU8 { + BitFieldU8(Map _values) + : values = Map.fromEntries( + List.generate(8, (i) => MapEntry(i, Bit(_values[i] ?? false)))); + + BitFieldU8.fromU8(int u8) + : values = Map.fromEntries( + List.generate(8, (i) => MapEntry(i, Bit.fromU8(u8, i)))); + + Map values; + + Bit value(int offset) { + assert(0 <= offset && offset < 8); + return values[offset]!; + } + + int toU8() => List.generate(8, (i) => oneHot(values[i]!.val, i)) + .fold(0, (acc, val) => acc | val); +} + +extension IntExt on int { + int wrappingAddU8(int rhs) { + return (this + rhs).toU8(); + } + + int wrappingAddU16(int rhs) { + return (this + rhs).toU16(); + } + + int wrappingSubU8(int rhs) { + return (this - rhs).toU8(); + } + + int wrappingSubU16(int rhs) { + return (this - rhs).toU16(); + } + + int rotateLeftU8() { + final left = isSet(this, 7); + return ((this << 1) | (left ? 1 : 0)).toU8(); + } + + int rotateRightU8() { + final right = isSet(this, 0); + return (this >> 1).toU8() | oneHot(right, 7); + } + + int toU8() { + return this & 0xFF; + } + + int toU16() { + return this & 0xFFFF; + } + + int toI8() { + if (!isSet(this, 7)) return this; + + return this - 0x0100; + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..8eafa74 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,670 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:math'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:dartboy/emulator/gameboy.dart'; +import 'package:dartboy/emulator/joypad.dart'; +import 'package:dartboy/emulator/rom.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/services.dart'; + +const _width = 160.0; +const _height = 144.0; +const _primaryColor = Color.fromARGB(255, 67, 57, 180); +const _buttonColor = Color.fromARGB(255, 140, 25, 82); +const _directionColor = Color.fromARGB(255, 22, 24, 29); +const _gbColor = Color.fromARGB(255, 204, 202, 195); +const _seColor = Color.fromARGB(255, 117, 116, 118); +const _paddingColor = Color.fromARGB(255, 99, 97, 110); +const _screenColor = Color.fromARGB(255, 98, 122, 3); + +void main() { + final parentRx = ReceivePort(); + + Isolate.spawn(_launchGameboy, parentRx.sendPort); + + ValueNotifier image = ValueNotifier(null); + ValueNotifier fps = ValueNotifier(0); + SendPort? childTx; + + parentRx.listen((e) { + if (e is SendPort) { + childTx = e; + } + + if (e is RenderFrameEvent) { + ui.decodeImageFromPixels(e.frame, 256, 256, ui.PixelFormat.rgba8888, + (result) { + image.value = result; + }); + } + + if (e is FpsUpdateEvent) { + fps.value = e.fps; + } + }); + + runApp(MyApp( + image: image, + fps: fps, + onRomSelected: (bytes) { + childTx?.send(FileSelectedEvent(bytes)); + }, + onKeyPressed: (key) { + childTx?.send(KeyPressedEvent(key)); + }, + onKeyReleased: (key) { + childTx?.send(KeyReleasedEvent(key)); + }, + )); +} + +void _launchGameboy(SendPort parentTx) async { + final childRx = ReceivePort(); + + parentTx.send(childRx.sendPort); + + final gb = GameBoy(); + + childRx.listen((message) { + if (message is FileSelectedEvent) { + final rom = Rom(message.bytes); + + gb.load(rom); + gb.reset(); + } + + if (message is KeyPressedEvent) { + if (gb.ready) gb.press(message.key); + } + + if (message is KeyReleasedEvent) { + if (gb.ready) gb.release(message.key); + } + }); + + var prevDateTime = DateTime.now(); + var frameCount = 0; + var fpsTotal = 0.0; + var sleep = const Duration(milliseconds: 16); + + while (true) { + if (gb.ready) { + for (var i = 0; i < 70224; i++) { + gb.tick(); + } + + final pixels = gb.render(); + + parentTx.send(RenderFrameEvent(pixels)); + } + + frameCount += 1; + + await Future.delayed(sleep); + + final current = DateTime.now(); + final elapsed = current.difference(prevDateTime); + final fps = (1000 / elapsed.inMilliseconds).clamp(0, 80); + + sleep = Duration( + milliseconds: max((sleep.inMilliseconds + (fps - 60.0)).floor(), 0), + ); + + fpsTotal += fps; + + if (frameCount >= 60) { + parentTx.send(FpsUpdateEvent((fpsTotal / frameCount).floor())); + + frameCount = 0; + fpsTotal = 0; + } + + prevDateTime = current; + } +} + +class FpsUpdateEvent { + FpsUpdateEvent(this.fps); + + final int fps; +} + +class RenderFrameEvent { + RenderFrameEvent(this.frame); + + final Uint8List frame; +} + +class FileSelectedEvent { + FileSelectedEvent(this.bytes); + + final Uint8List bytes; +} + +class KeyPressedEvent { + KeyPressedEvent(this.key); + + final JoypadKey key; +} + +class KeyReleasedEvent { + KeyReleasedEvent(this.key); + + final JoypadKey key; +} + +class MyApp extends StatelessWidget { + const MyApp({ + Key? key, + required this.image, + required this.fps, + required this.onRomSelected, + required this.onKeyPressed, + required this.onKeyReleased, + }) : super(key: key); + + final ValueNotifier fps; + final ValueNotifier image; + final void Function(Uint8List) onRomSelected; + final void Function(JoypadKey) onKeyPressed; + final void Function(JoypadKey) onKeyReleased; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'DART BOY', + theme: ThemeData( + primaryColor: _primaryColor, + canvasColor: _gbColor, + ), + home: MyHomePage( + title: 'DART BOY', + fps: fps, + image: image, + onRomSelected: onRomSelected, + onKeyPressed: onKeyPressed, + onKeyReleased: onKeyReleased, + ), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({ + Key? key, + required this.title, + required this.fps, + required this.image, + required this.onRomSelected, + required this.onKeyPressed, + required this.onKeyReleased, + }) : super(key: key); + + final String title; + final ValueNotifier fps; + final ValueNotifier image; + final void Function(Uint8List) onRomSelected; + final void Function(JoypadKey) onKeyPressed; + final void Function(JoypadKey) onKeyReleased; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + foregroundColor: _primaryColor, + backgroundColor: _gbColor, + title: Text(widget.title, + style: const TextStyle( + fontSize: 32.0, + fontWeight: FontWeight.w800, + fontStyle: FontStyle.italic, + )), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: _Screen( + fps: widget.fps, + image: widget.image, + ), + ), + Expanded( + child: _Controller( + onKeyPressed: widget.onKeyPressed, + onKeyReleased: widget.onKeyReleased, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + final result = await FilePicker.platform.pickFiles(); + if (result == null) return; + + var bytes = result.files.first.bytes; + + if (bytes == null) { + final file = File(result.paths.first!); + + bytes = await file.readAsBytes(); + } + + widget.onRomSelected(bytes); + }, + backgroundColor: _seColor, + child: const Icon( + Icons.file_upload, + color: Colors.white, + )), + ); + } +} + +class _Screen extends StatefulWidget { + const _Screen({ + Key? key, + required this.fps, + required this.image, + }) : super(key: key); + + final ValueNotifier fps; + final ValueNotifier image; + + @override + State createState() => _ScreenState(); +} + +class _ScreenState extends State<_Screen> { + @override + void initState() { + super.initState(); + + widget.image.addListener(_rebuild); + } + + @override + void dispose() { + super.dispose(); + widget.image.removeListener(_rebuild); + } + + void _rebuild() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: _paddingColor, + child: FittedBox( + child: Card( + elevation: 10, + clipBehavior: Clip.antiAlias, + child: CustomPaint( + painter: _ScreenPainter( + image: widget.image.value, + ), + child: SizedBox( + width: _width, + height: _height, + child: Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + widget.fps.value.toString(), + style: const TextStyle( + color: _primaryColor, + fontSize: 6.0, + ), + ), + ), + ), + ), + ), + ), + ), + ); + } +} + +class _Controller extends StatelessWidget { + const _Controller({ + Key? key, + required this.onKeyPressed, + required this.onKeyReleased, + }) : super(key: key); + + final void Function(JoypadKey key) onKeyPressed; + final void Function(JoypadKey key) onKeyReleased; + + static final _keyToJoypadKeyMap = { + LogicalKeyboardKey.keyZ: JoypadKey.a, + LogicalKeyboardKey.keyX: JoypadKey.b, + LogicalKeyboardKey.keyV: JoypadKey.start, + LogicalKeyboardKey.keyC: JoypadKey.select, + LogicalKeyboardKey.arrowUp: JoypadKey.up, + LogicalKeyboardKey.arrowDown: JoypadKey.down, + LogicalKeyboardKey.arrowRight: JoypadKey.right, + LogicalKeyboardKey.arrowLeft: JoypadKey.left, + }; + + @override + Widget build(BuildContext context) { + return KeyboardListener( + onKeyEvent: (key) { + final joypadKey = _keyToJoypadKeyMap[key.logicalKey]; + if (joypadKey == null) return; + + if (key is KeyDownEvent) { + onKeyPressed(joypadKey); + } + if (key is KeyUpEvent) { + onKeyReleased(joypadKey); + } + }, + focusNode: FocusNode(), + autofocus: true, + child: FittedBox( + child: SizedBox( + width: 500, + height: 400, + child: ClipPath( + clipBehavior: Clip.antiAlias, + child: CustomPaint( + painter: const _ControllerPainter(), + child: Stack( + children: [ + Positioned( + top: 100, + right: 40, + width: 60, + height: 40, + child: _ControllerButton( + onPressed: () => onKeyPressed(JoypadKey.a), + onReleased: () => onKeyReleased(JoypadKey.a), + color: _buttonColor, + child: const Text( + 'A', + style: TextStyle( + color: Colors.white, + ), + ), + ), + ), + Positioned( + top: 100, + right: 110, + width: 60, + height: 40, + child: _ControllerButton( + onPressed: () => onKeyPressed(JoypadKey.b), + onReleased: () => onKeyReleased(JoypadKey.b), + color: _buttonColor, + child: const Text( + 'B', + style: TextStyle( + color: Colors.white, + ), + ), + ), + ), + Positioned( + top: 100, + left: 30, + width: 50, + height: 50, + child: _ControllerButton( + onPressed: () => onKeyPressed(JoypadKey.left), + onReleased: () => onKeyReleased(JoypadKey.left), + color: _directionColor, + child: const Icon( + Icons.arrow_left_rounded, + color: Colors.white, + semanticLabel: 'left', + ), + ), + ), + Positioned( + top: 100, + left: 130, + width: 50, + height: 50, + child: _ControllerButton( + onPressed: () => onKeyPressed(JoypadKey.right), + onReleased: () => onKeyReleased(JoypadKey.right), + color: _directionColor, + child: const Icon( + Icons.arrow_right_rounded, + color: Colors.white, + semanticLabel: 'right', + ), + ), + ), + Positioned( + top: 50, + left: 80, + width: 50, + height: 50, + child: _ControllerButton( + onPressed: () => onKeyPressed(JoypadKey.up), + onReleased: () => onKeyReleased(JoypadKey.up), + color: _directionColor, + child: const Icon( + Icons.arrow_drop_up_sharp, + color: Colors.white, + semanticLabel: 'up', + ), + ), + ), + Positioned( + top: 150, + left: 80, + width: 50, + height: 50, + child: _ControllerButton( + onPressed: () => onKeyPressed(JoypadKey.down), + onReleased: () => onKeyReleased(JoypadKey.down), + color: _directionColor, + child: const Icon( + Icons.arrow_drop_down_sharp, + color: Colors.white, + semanticLabel: 'down', + ), + ), + ), + Positioned( + bottom: 80, + left: 0, + right: 0, + height: 40, + child: Center( + child: SizedBox( + width: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Transform.rotate( + angle: -1 / 6 * pi, + child: SizedBox( + width: 100, + height: double.infinity, + child: _ControllerButton( + onPressed: () => + onKeyPressed(JoypadKey.select), + onReleased: () => + onKeyReleased(JoypadKey.select), + color: _seColor, + child: const Text( + 'SELECT', + style: TextStyle( + color: Colors.white, + ), + ), + ), + ), + ), + Transform.rotate( + angle: -1 / 6 * pi, + child: SizedBox( + width: 100, + height: double.infinity, + child: _ControllerButton( + onPressed: () => + onKeyPressed(JoypadKey.start), + onReleased: () => + onKeyReleased(JoypadKey.start), + color: _seColor, + child: const Text( + 'START', + style: TextStyle( + color: Colors.white, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} + +class _ControllerButton extends StatelessWidget { + const _ControllerButton({ + Key? key, + required this.color, + required this.onPressed, + required this.onReleased, + required this.child, + }) : super(key: key); + + final Color color; + final void Function() onPressed; + final void Function() onReleased; + final Widget child; + + @override + Widget build(BuildContext context) { + return Semantics( + button: true, + child: Material( + color: color, + elevation: 5, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + child: InkWell( + onTapDown: (_) => onPressed(), + onTap: () => onReleased(), + onTapCancel: () => onReleased(), + child: Center(child: child), + ), + ), + ); + } +} + +class _ScreenPainter extends CustomPainter { + const _ScreenPainter({ + required this.image, + }); + + final ui.Image? image; + + @override + void paint(ui.Canvas canvas, ui.Size size) { + final paint = Paint()..color = _screenColor; + + canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); + + if (image != null) { + canvas.drawImageRect( + image!, + const Rect.fromLTWH(0, 0, _width, _height), + Rect.fromLTWH( + 0, + 0, + size.width, + size.height, + ), + paint, + ); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} + +class _ControllerPainter extends CustomPainter { + const _ControllerPainter(); + + @override + void paint(ui.Canvas canvas, ui.Size size) { + const color = Color.fromARGB(60, 117, 113, 103); + final paint = Paint()..color = color; + + const width = 16.0; + const height = 80.0; + + canvas.translate( + size.width - 2 * 6 * width - 10.0, + size.height - height - 10.0, + ); + canvas.rotate(-1 / 6 * pi); + + for (int i = 0; i < 6; i++) { + final x = width * i * 2.0; + const y = 0.0; + canvas.drawRRect( + RRect.fromLTRBR( + x, + y, + x + width, + y + height, + const Radius.circular(4.0), + ), + paint, + ); + } + + canvas.drawRect( + const Rect.fromLTWH( + -100, + height - 30, + 2 * 6 * width + 200, + 200, + ), + paint, + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..ed516fa --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.13.0 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f8fe547 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,783 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 28357809A88CF6D9A1255918 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B151D3C397335FA112BA759 /* Pods_RunnerTests.framework */; }; + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + D6F93CF7F0CA36252F4AD3D2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D1874941DD33C9FCF1F1F69 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1A2E2B151B48AC4DFB2AD9BB /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 1D1874941DD33C9FCF1F1F69 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* dartboy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dartboy.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 564410B953FF6C77EBE6F085 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8B151D3C397335FA112BA759 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + B38C2219C9F50F737C38A392 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D63B22D6841E86B41A2E260C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + D794D0297BC1764A237C58FA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + FA503B08EE1C81F4686234B5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 28357809A88CF6D9A1255918 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D6F93CF7F0CA36252F4AD3D2 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 43EC50EF23A4F7D9F6413315 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* dartboy.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 43EC50EF23A4F7D9F6413315 /* Pods */ = { + isa = PBXGroup; + children = ( + D63B22D6841E86B41A2E260C /* Pods-Runner.debug.xcconfig */, + D794D0297BC1764A237C58FA /* Pods-Runner.release.xcconfig */, + 564410B953FF6C77EBE6F085 /* Pods-Runner.profile.xcconfig */, + 1A2E2B151B48AC4DFB2AD9BB /* Pods-RunnerTests.debug.xcconfig */, + B38C2219C9F50F737C38A392 /* Pods-RunnerTests.release.xcconfig */, + FA503B08EE1C81F4686234B5 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1D1874941DD33C9FCF1F1F69 /* Pods_Runner.framework */, + 8B151D3C397335FA112BA759 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 1E18CDAC25CABBC49594A994 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 52BA8C51C1BE45EC533C2FB9 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* dartboy.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1E18CDAC25CABBC49594A994 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 52BA8C51C1BE45EC533C2FB9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1A2E2B151B48AC4DFB2AD9BB /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dartboy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dartboy"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B38C2219C9F50F737C38A392 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dartboy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dartboy"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FA503B08EE1C81F4686234B5 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dartboy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dartboy"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..6a6149e --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..17c351b --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = dartboy + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.dartboy + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..bce5fe6 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,274 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "486b7bc707424572cdf7bd7e812a0c146de3fd47ecadf070254cc60383f21dd8" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "824f5b9f389bfc4dddac3dea76cd70c51092d9dff0b2ece7ef4f53db8547d258" + url: "https://pub.dev" + source: hosted + version: "8.0.6" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" + url: "https://pub.dev" + source: hosted + version: "2.0.21" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.dev" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + win32: + dependency: transitive + description: + name: win32 + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 + url: "https://pub.dev" + source: hosted + version: "5.5.1" +sdks: + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..a46e859 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,25 @@ +name: dartboy +description: dartboy + +publish_to: "none" + +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + cupertino_icons: ^1.0.2 + file_picker: ^8.0.6 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^1.0.0 + +flutter: + uses-material-design: true diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..a46648f --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:dartboy/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + // await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}