Compare commits

...

2 Commits

Author SHA1 Message Date
8d0487be8b 基本可用 2024-08-16 14:51:15 +08:00
3af171bdc3 OptimeGBA 一致基本归档 2024-08-16 11:06:40 +08:00
134 changed files with 13573 additions and 10810 deletions

994
Assets/Game.unity Normal file
View File

@ -0,0 +1,994 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.37311953, g: 0.38074014, b: 0.3587274, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
m_GIWorkflowMode: 1
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 2
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &54588347
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 54588348}
- component: {fileID: 54588350}
- component: {fileID: 54588349}
m_Layer: 5
m_Name: Text
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &54588348
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 54588347}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1712987038}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &54588349
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 54588347}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MaxSize: 40
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: 'START
'
--- !u!222 &54588350
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 54588347}
m_CullTransparentMesh: 1
--- !u!1 &77869763
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 77869764}
- component: {fileID: 77869766}
- component: {fileID: 77869765}
m_Layer: 0
m_Name: AudioProvider
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &77869764
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 77869763}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 706021509}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!82 &77869765
AudioSource:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 77869763}
m_Enabled: 1
serializedVersion: 4
OutputAudioMixerGroup: {fileID: 0}
m_audioClip: {fileID: 0}
m_PlayOnAwake: 1
m_Volume: 1
m_Pitch: 1
Loop: 0
Mute: 0
Spatialize: 0
SpatializePostEffects: 0
Priority: 128
DopplerLevel: 1
MinDistance: 1
MaxDistance: 500
Pan2D: 0
rolloffMode: 0
BypassEffects: 0
BypassListenerEffects: 0
BypassReverbZones: 0
rolloffCustomCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 1
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 1
value: 0
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
panLevelCustomCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
spreadCustomCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
reverbZoneMixCustomCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 1
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
--- !u!114 &77869766
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 77869763}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: af17dd239b0eea44592e0cd125b2e3b9, type: 3}
m_Name:
m_EditorClassIdentifier:
m_as: {fileID: 77869765}
--- !u!1 &109485475
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 109485478}
- component: {fileID: 109485477}
- component: {fileID: 109485476}
m_Layer: 0
m_Name: EventSystem
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &109485476
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 109485475}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
m_Name:
m_EditorClassIdentifier:
m_HorizontalAxis: Horizontal
m_VerticalAxis: Vertical
m_SubmitButton: Submit
m_CancelButton: Cancel
m_InputActionsPerSecond: 10
m_RepeatDelay: 0.5
m_ForceModuleActive: 0
--- !u!114 &109485477
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 109485475}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
m_Name:
m_EditorClassIdentifier:
m_FirstSelected: {fileID: 0}
m_sendNavigationEvents: 1
m_DragThreshold: 10
--- !u!4 &109485478
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 109485475}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &706021508
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 706021509}
- component: {fileID: 706021510}
m_Layer: 0
m_Name: Emulator
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &706021509
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 706021508}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 1348932687}
- {fileID: 77869764}
- {fileID: 884831573}
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &706021510
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 706021508}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 515e71167c74b044984b170a2a141f10, type: 3}
m_Name:
m_EditorClassIdentifier:
videoProvider: {fileID: 1348932686}
audioProvider: {fileID: 77869766}
ShowBackBuf: 0
RunEmulator: 0
EnableAudio: 1
BootBIOS: 0
audioGain: 1
btnStart: {fileID: 1712987039}
--- !u!1 &763702298
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 763702300}
- component: {fileID: 763702299}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!108 &763702299
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 763702298}
m_Enabled: 1
serializedVersion: 10
m_Type: 1
m_Shape: 0
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize: 10
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ShadowRadius: 0
m_ShadowAngle: 0
--- !u!4 &763702300
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 763702298}
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!1 &884831572
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 884831573}
- component: {fileID: 884831576}
- component: {fileID: 884831575}
- component: {fileID: 884831574}
- component: {fileID: 884831577}
m_Layer: 5
m_Name: Canvas
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &884831573
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 884831572}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_Children:
- {fileID: 1537688724}
- {fileID: 1712987038}
m_Father: {fileID: 706021509}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!114 &884831574
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 884831572}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!114 &884831575
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 884831572}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UiScaleMode: 0
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 800, y: 600}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!223 &884831576
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 884831572}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_AdditionalShaderChannelsFlag: 0
m_SortingLayerID: 0
m_SortingOrder: 0
m_TargetDisplay: 0
--- !u!114 &884831577
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 884831572}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2a6d4732564041e4d85a5fbef37546d6, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &1348932685
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1348932687}
- component: {fileID: 1348932686}
m_Layer: 0
m_Name: VideoProvider
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1348932686
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1348932685}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 331cd2e4e0a49874cab11aa18b69e827, type: 3}
m_Name:
m_EditorClassIdentifier:
m_drawCanvas: {fileID: 1537688722}
--- !u!4 &1348932687
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1348932685}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 706021509}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1421561889
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1421561892}
- component: {fileID: 1421561891}
- component: {fileID: 1421561890}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &1421561890
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1421561889}
m_Enabled: 1
--- !u!20 &1421561891
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1421561889}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &1421561892
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1421561889}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1537688721
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1537688724}
- component: {fileID: 1537688723}
- component: {fileID: 1537688722}
m_Layer: 5
m_Name: RawImage
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1537688722
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1537688721}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Texture: {fileID: 0}
m_UVRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
--- !u!222 &1537688723
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1537688721}
m_CullTransparentMesh: 1
--- !u!224 &1537688724
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1537688721}
m_LocalRotation: {x: 1, y: 0, z: 0, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 884831573}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 180, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0}
m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 1280, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1712987037
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1712987038}
- component: {fileID: 1712987041}
- component: {fileID: 1712987040}
- component: {fileID: 1712987039}
m_Layer: 5
m_Name: btnStart
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1712987038
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1712987037}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 54588348}
m_Father: {fileID: 884831573}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -471, y: -281}
m_SizeDelta: {x: 160, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1712987039
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1712987037}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 1712987040}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!114 &1712987040
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1712987037}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1712987041
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1712987037}
m_CullTransparentMesh: 1

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 9fc0d4010bbf28b4594072e72b8655ab
guid: 88a72b3d9c54eff4e991fdae233d3452
DefaultImporter:
externalObjects: {}
userData:

File diff suppressed because it is too large Load Diff

View File

@ -1,600 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
namespace Iris.CPU
{
public sealed class CPU_Core
{
public enum Model
{
ARM7TDMI,
ARM946ES
}
public delegate Byte Read8_Delegate(UInt32 address);
public delegate UInt16 Read16_Delegate(UInt32 address);
public delegate UInt32 Read32_Delegate(UInt32 address);
public delegate void Write8_Delegate(UInt32 address, Byte value);
public delegate void Write16_Delegate(UInt32 address, UInt16 value);
public delegate void Write32_Delegate(UInt32 address, UInt32 value);
public delegate UInt64 HandleSWI_Delegate();
public delegate UInt64 HandleIRQ_Delegate();
// could have used function pointers (delegate*) for performance instead of delegates but it's less flexible (cannot use non-static function for instance)
//public readonly struct CallbackInterface
//(
// Read8_Delegate read8,
// Read16_Delegate read16,
// Read32_Delegate read32,
// Write8_Delegate write8,
// Write16_Delegate write16,
// Write32_Delegate write32,
// HandleSWI_Delegate handleSWI,
// HandleIRQ_Delegate handleIRQ
//)
//{
// internal readonly Read8_Delegate _read8 = read8;
// internal readonly Read16_Delegate _read16 = read16;
// internal readonly Read32_Delegate _read32 = read32;
// internal readonly Write8_Delegate _write8 = write8;
// internal readonly Write16_Delegate _write16 = write16;
// internal readonly Write32_Delegate _write32 = write32;
// internal readonly HandleSWI_Delegate _handleSWI = handleSWI;
// internal readonly HandleIRQ_Delegate _handleIRQ = handleIRQ;
//}
public readonly struct CallbackInterface
{
public CallbackInterface
(
Read8_Delegate read8,
Read16_Delegate read16,
Read32_Delegate read32,
Write8_Delegate write8,
Write16_Delegate write16,
Write32_Delegate write32,
HandleSWI_Delegate handleSWI,
HandleIRQ_Delegate handleIRQ
)
{
_read8 = read8;
_read16 = read16;
_read32 = read32;
_write8 = write8;
_write16 = write16;
_write32 = write32;
_handleSWI = handleSWI;
_handleIRQ = handleIRQ;
}
internal readonly Read8_Delegate _read8;
internal readonly Read16_Delegate _read16;
internal readonly Read32_Delegate _read32;
internal readonly Write8_Delegate _write8;
internal readonly Write16_Delegate _write16;
internal readonly Write32_Delegate _write32;
internal readonly HandleSWI_Delegate _handleSWI;
internal readonly HandleIRQ_Delegate _handleIRQ;
}
public enum Signal
{
High,
Low
}
internal enum Flag
{
V = 28,
C = 29,
Z = 30,
N = 31
}
//internal unsafe readonly struct InstructionListEntry1<T>(T mask, T expected, delegate*<CPU_Core, T, UInt64> handler, List<Model> modelList)
//{
// internal readonly T _mask = mask;
// internal readonly T _expected = expected;
// internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler = handler;
// internal readonly List<Model> _modelList = modelList;
//}
internal unsafe readonly struct InstructionListEntry<T>
{
internal InstructionListEntry(T mask, T expected, delegate*<CPU_Core, T, UInt64> handler, List<Model> modelList)
{
_mask = mask;
_expected = expected;
_handler = handler;
_modelList = modelList;
}
internal readonly T _mask;
internal readonly T _expected;
internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler;
internal readonly List<Model> _modelList;
}
//internal unsafe readonly struct InstructionLUTEntry<T>(delegate*<CPU_Core, T, UInt64> handler)
//{
// internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler = handler;
//}
internal unsafe readonly struct InstructionLUTEntry<T>
{
internal InstructionLUTEntry(delegate*<CPU_Core, T, UInt64> handler)
{
_handler = handler;
}
internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler;
}
internal const UInt32 ModeMask = 0b1_1111;
internal const UInt32 UserMode = 0b1_0000;
internal const UInt32 SystemMode = 0b1_1111;
internal const UInt32 SupervisorMode = 0b1_0011;
internal const UInt32 AbortMode = 0b1_0111;
internal const UInt32 UndefinedMode = 0b1_1011;
internal const UInt32 InterruptMode = 0b1_0010;
internal const UInt32 FastInterruptMode = 0b1_0001;
public const UInt32 SP = 13;
public const UInt32 LR = 14;
public const UInt32 PC = 15;
public readonly UInt32[] Reg = new UInt32[16];
public UInt32 CPSR;
public UInt32 SPSR;
public UInt32 Reg8_usr, Reg9_usr, Reg10_usr, Reg11_usr, Reg12_usr, Reg13_usr, Reg14_usr;
public UInt32 Reg13_svc, Reg14_svc;
public UInt32 Reg13_abt, Reg14_abt;
public UInt32 Reg13_und, Reg14_und;
public UInt32 Reg13_irq, Reg14_irq;
public UInt32 Reg8_fiq, Reg9_fiq, Reg10_fiq, Reg11_fiq, Reg12_fiq, Reg13_fiq, Reg14_fiq;
public UInt32 SPSR_svc, SPSR_abt, SPSR_und, SPSR_irq, SPSR_fiq;
internal readonly Model _model;
internal readonly CallbackInterface _callbackInterface;
private readonly ARM_Interpreter _armInterpreter;
private readonly THUMB_Interpreter _thumbInterpreter;
public UInt32 NextInstructionAddress;
public Signal NIRQ;
public CPU_Core(Model model, CallbackInterface callbackInterface)
{
_model = model;
_callbackInterface = callbackInterface;
_armInterpreter = new(this);
_thumbInterpreter = new(this);
}
public void ResetState()
{
Array.Clear(Reg,0,Reg.Length);
CPSR = 0b1_0000;
SPSR = 0;
Reg8_usr = 0;
Reg9_usr = 0;
Reg10_usr = 0;
Reg11_usr = 0;
Reg12_usr = 0;
Reg13_usr = 0;
Reg14_usr = 0;
Reg13_svc = 0;
Reg14_svc = 0;
Reg13_abt = 0;
Reg14_abt = 0;
Reg13_und = 0;
Reg14_und = 0;
Reg13_irq = 0;
Reg14_irq = 0;
Reg8_fiq = 0;
Reg9_fiq = 0;
Reg10_fiq = 0;
Reg11_fiq = 0;
Reg12_fiq = 0;
Reg13_fiq = 0;
Reg14_fiq = 0;
SPSR_svc = 0;
SPSR_abt = 0;
SPSR_und = 0;
SPSR_irq = 0;
SPSR_fiq = 0;
NextInstructionAddress = 0;
NIRQ = Signal.High;
}
public void LoadState(BinaryReader reader)
{
foreach (ref UInt32 reg in Reg.AsSpan())
reg = reader.ReadUInt32();
CPSR = reader.ReadUInt32();
SPSR = reader.ReadUInt32();
Reg8_usr = reader.ReadUInt32();
Reg9_usr = reader.ReadUInt32();
Reg10_usr = reader.ReadUInt32();
Reg11_usr = reader.ReadUInt32();
Reg12_usr = reader.ReadUInt32();
Reg13_usr = reader.ReadUInt32();
Reg14_usr = reader.ReadUInt32();
Reg13_svc = reader.ReadUInt32();
Reg14_svc = reader.ReadUInt32();
Reg13_abt = reader.ReadUInt32();
Reg14_abt = reader.ReadUInt32();
Reg13_und = reader.ReadUInt32();
Reg14_und = reader.ReadUInt32();
Reg13_irq = reader.ReadUInt32();
Reg14_irq = reader.ReadUInt32();
Reg8_fiq = reader.ReadUInt32();
Reg9_fiq = reader.ReadUInt32();
Reg10_fiq = reader.ReadUInt32();
Reg11_fiq = reader.ReadUInt32();
Reg12_fiq = reader.ReadUInt32();
Reg13_fiq = reader.ReadUInt32();
Reg14_fiq = reader.ReadUInt32();
SPSR_svc = reader.ReadUInt32();
SPSR_abt = reader.ReadUInt32();
SPSR_und = reader.ReadUInt32();
SPSR_irq = reader.ReadUInt32();
SPSR_fiq = reader.ReadUInt32();
NextInstructionAddress = reader.ReadUInt32();
NIRQ = (Signal)reader.ReadInt32();
}
public void SaveState(BinaryWriter writer)
{
foreach (UInt32 reg in Reg)
writer.Write(reg);
writer.Write(CPSR);
writer.Write(SPSR);
writer.Write(Reg8_usr);
writer.Write(Reg9_usr);
writer.Write(Reg10_usr);
writer.Write(Reg11_usr);
writer.Write(Reg12_usr);
writer.Write(Reg13_usr);
writer.Write(Reg14_usr);
writer.Write(Reg13_svc);
writer.Write(Reg14_svc);
writer.Write(Reg13_abt);
writer.Write(Reg14_abt);
writer.Write(Reg13_und);
writer.Write(Reg14_und);
writer.Write(Reg13_irq);
writer.Write(Reg14_irq);
writer.Write(Reg8_fiq);
writer.Write(Reg9_fiq);
writer.Write(Reg10_fiq);
writer.Write(Reg11_fiq);
writer.Write(Reg12_fiq);
writer.Write(Reg13_fiq);
writer.Write(Reg14_fiq);
writer.Write(SPSR_svc);
writer.Write(SPSR_abt);
writer.Write(SPSR_und);
writer.Write(SPSR_irq);
writer.Write(SPSR_fiq);
writer.Write(NextInstructionAddress);
writer.Write((int)NIRQ);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UInt64 Step()
{
UInt32 i = (CPSR >> 7) & 1;
if ((i == 0) && (NIRQ == Signal.Low))
return _callbackInterface._handleIRQ();
UInt32 t = (CPSR >> 5) & 1;
if (t == 0)
return _armInterpreter.Step();
else
return _thumbInterpreter.Step();
}
public void SetCPSR(UInt32 value)
{
UInt32 previousMode = CPSR & ModeMask;
UInt32 newMode = value & ModeMask;
CPSR = value | 0b1_0000;
if (previousMode != newMode)
{
ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(Reg);
ref UInt32 reg8 = ref Unsafe.Add(ref regDataRef, 8);
ref UInt32 reg9 = ref Unsafe.Add(ref regDataRef, 9);
ref UInt32 reg10 = ref Unsafe.Add(ref regDataRef, 10);
ref UInt32 reg11 = ref Unsafe.Add(ref regDataRef, 11);
ref UInt32 reg12 = ref Unsafe.Add(ref regDataRef, 12);
ref UInt32 reg13 = ref Unsafe.Add(ref regDataRef, 13);
ref UInt32 reg14 = ref Unsafe.Add(ref regDataRef, 14);
// save previous mode registers
switch (previousMode)
{
case UserMode:
case SystemMode:
Reg8_usr = reg8;
Reg9_usr = reg9;
Reg10_usr = reg10;
Reg11_usr = reg11;
Reg12_usr = reg12;
Reg13_usr = reg13;
Reg14_usr = reg14;
break;
case SupervisorMode:
Reg8_usr = reg8;
Reg9_usr = reg9;
Reg10_usr = reg10;
Reg11_usr = reg11;
Reg12_usr = reg12;
Reg13_svc = reg13;
Reg14_svc = reg14;
SPSR_svc = SPSR;
break;
case AbortMode:
Reg8_usr = reg8;
Reg9_usr = reg9;
Reg10_usr = reg10;
Reg11_usr = reg11;
Reg12_usr = reg12;
Reg13_abt = reg13;
Reg14_abt = reg14;
SPSR_abt = SPSR;
break;
case UndefinedMode:
Reg8_usr = reg8;
Reg9_usr = reg9;
Reg10_usr = reg10;
Reg11_usr = reg11;
Reg12_usr = reg12;
Reg13_und = reg13;
Reg14_und = reg14;
SPSR_und = SPSR;
break;
case InterruptMode:
Reg8_usr = reg8;
Reg9_usr = reg9;
Reg10_usr = reg10;
Reg11_usr = reg11;
Reg12_usr = reg12;
Reg13_irq = reg13;
Reg14_irq = reg14;
SPSR_irq = SPSR;
break;
case FastInterruptMode:
Reg8_fiq = reg8;
Reg9_fiq = reg9;
Reg10_fiq = reg10;
Reg11_fiq = reg11;
Reg12_fiq = reg12;
Reg13_fiq = reg13;
Reg14_fiq = reg14;
SPSR_fiq = SPSR;
break;
}
// load new mode registers
switch (newMode)
{
case UserMode:
case SystemMode:
reg8 = Reg8_usr;
reg9 = Reg9_usr;
reg10 = Reg10_usr;
reg11 = Reg11_usr;
reg12 = Reg12_usr;
reg13 = Reg13_usr;
reg14 = Reg14_usr;
break;
case SupervisorMode:
reg8 = Reg8_usr;
reg9 = Reg9_usr;
reg10 = Reg10_usr;
reg11 = Reg11_usr;
reg12 = Reg12_usr;
reg13 = Reg13_svc;
reg14 = Reg14_svc;
SPSR = SPSR_svc;
break;
case AbortMode:
reg8 = Reg8_usr;
reg9 = Reg9_usr;
reg10 = Reg10_usr;
reg11 = Reg11_usr;
reg12 = Reg12_usr;
reg13 = Reg13_abt;
reg14 = Reg14_abt;
SPSR = SPSR_abt;
break;
case UndefinedMode:
reg8 = Reg8_usr;
reg9 = Reg9_usr;
reg10 = Reg10_usr;
reg11 = Reg11_usr;
reg12 = Reg12_usr;
reg13 = Reg13_und;
reg14 = Reg14_und;
SPSR = SPSR_und;
break;
case InterruptMode:
reg8 = Reg8_usr;
reg9 = Reg9_usr;
reg10 = Reg10_usr;
reg11 = Reg11_usr;
reg12 = Reg12_usr;
reg13 = Reg13_irq;
reg14 = Reg14_irq;
SPSR = SPSR_irq;
break;
case FastInterruptMode:
reg8 = Reg8_fiq;
reg9 = Reg9_fiq;
reg10 = Reg10_fiq;
reg11 = Reg11_fiq;
reg12 = Reg12_fiq;
reg13 = Reg13_fiq;
reg14 = Reg14_fiq;
SPSR = SPSR_fiq;
break;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal UInt32 GetFlag(Flag flag)
{
return (CPSR >> (int)flag) & 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SetFlag(Flag flag, UInt32 value)
{
CPSR = (CPSR & ~(1u << (int)flag)) | (value << (int)flag);
}
internal bool ConditionPassed(UInt32 cond)
{
return cond switch
{
// EQ
0b0000 => GetFlag(Flag.Z) == 1,
// NE
0b0001 => GetFlag(Flag.Z) == 0,
// CS/HS
0b0010 => GetFlag(Flag.C) == 1,
// CC/LO
0b0011 => GetFlag(Flag.C) == 0,
// MI
0b0100 => GetFlag(Flag.N) == 1,
// PL
0b0101 => GetFlag(Flag.N) == 0,
// VS
0b0110 => GetFlag(Flag.V) == 1,
// VC
0b0111 => GetFlag(Flag.V) == 0,
// HI
0b1000 => (GetFlag(Flag.C) == 1) && (GetFlag(Flag.Z) == 0),
// LS
0b1001 => (GetFlag(Flag.C) == 0) || (GetFlag(Flag.Z) == 1),
// GE
0b1010 => GetFlag(Flag.N) == GetFlag(Flag.V),
// LT
0b1011 => GetFlag(Flag.N) != GetFlag(Flag.V),
// GT
0b1100 => (GetFlag(Flag.Z) == 0) && (GetFlag(Flag.N) == GetFlag(Flag.V)),
// LE
0b1101 => (GetFlag(Flag.Z) == 1) || (GetFlag(Flag.N) != GetFlag(Flag.V)),
// AL
0b1110 => true,
// NV
0b1111 => false,
// should never happen
_ => throw new Exception($"Iris.CPU.CPU_Core: Wrong condition code {cond}"),
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static UInt32 Not(UInt32 flag)
{
return flag ^ 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static UInt32 CarryFrom(UInt64 result)
{
return (result > 0xffff_ffff) ? 1u : 0u;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static UInt32 BorrowFrom(UInt64 result)
{
return (result >= 0x8000_0000_0000_0000) ? 1u : 0u;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static UInt32 OverflowFrom_Addition(UInt32 leftOperand, UInt32 rightOperand, UInt32 result)
{
return (((leftOperand >> 31) == (rightOperand >> 31))
&& ((leftOperand >> 31) != (result >> 31))) ? 1u : 0u;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static UInt32 OverflowFrom_Subtraction(UInt32 leftOperand, UInt32 rightOperand, UInt32 result)
{
return (((leftOperand >> 31) != (rightOperand >> 31))
&& ((leftOperand >> 31) != (result >> 31))) ? 1u : 0u;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static UInt32 ArithmeticShiftRight(UInt32 value, int shiftAmount)
{
return (UInt32)((Int32)value >> shiftAmount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static UInt32 SignExtend(UInt32 value, int size)
{
return value | ~((value & (1u << (size - 1))) - 1);
}
internal static UInt64 ComputeMultiplicationCycleCount(UInt32 leftMultiplier, UInt32 rightMultiplier)
{
static UInt64 ComputeMultiplierCycleCount(UInt32 multiplier)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool CheckMultiplierAgainstMask(UInt32 multiplier, UInt32 mask)
{
UInt32 masked = multiplier & mask;
return (masked == 0) || (masked == mask);
}
if (CheckMultiplierAgainstMask(multiplier, 0xffff_ff00))
return 1;
else if (CheckMultiplierAgainstMask(multiplier, 0xffff_0000))
return 2;
else if (CheckMultiplierAgainstMask(multiplier, 0xff00_0000))
return 3;
else
return 4;
}
return Math.Max(ComputeMultiplierCycleCount(leftMultiplier), ComputeMultiplierCycleCount(rightMultiplier));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,152 +0,0 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Iris.Common
{
public sealed class Scheduler
{
public Scheduler(int taskListSize, int scheduledTaskListSize)
{
_taskList = new Task_Delegate[taskListSize];
_scheduledTaskList = new ScheduledTaskListEntry[scheduledTaskListSize];
}
public delegate void Task_Delegate(UInt64 cycleCountDelay);
private readonly Task_Delegate[] _taskList;
//private readonly Task_Delegate[] _taskList = new Task_Delegate[taskListSize];
private struct ScheduledTaskListEntry
{
internal int _id;
internal UInt64 _cycleCount;
}
private readonly ScheduledTaskListEntry[] _scheduledTaskList; // sorted by _cycleCount from smallest to largest
private int _scheduledTaskCount;
private UInt64 _cycleCounter;
public void ResetState()
{
_scheduledTaskCount = 0;
_cycleCounter = 0;
}
public void LoadState(BinaryReader reader)
{
foreach (ref ScheduledTaskListEntry entry in _scheduledTaskList.AsSpan())
{
entry._id = reader.ReadInt32();
entry._cycleCount = reader.ReadUInt64();
}
_scheduledTaskCount = reader.ReadInt32();
_cycleCounter = reader.ReadUInt64();
}
public void SaveState(BinaryWriter writer)
{
foreach (ScheduledTaskListEntry entry in _scheduledTaskList)
{
writer.Write(entry._id);
writer.Write(entry._cycleCount);
}
writer.Write(_scheduledTaskCount);
writer.Write(_cycleCounter);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UInt64 GetCycleCounter()
{
return _cycleCounter;
}
public void AdvanceCycleCounter(UInt64 cycleCount)
{
_cycleCounter += cycleCount;
// process tasks
//ref readonly ScheduledTaskListEntry firstEntry = ref UnSafeCommon.GetArrayDataReference(_scheduledTaskList);
//ref Task_Delegate taskListDataRef = ref UnSafeCommon.GetArrayDataReference(_taskList);
ref readonly ScheduledTaskListEntry firstEntry = ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList);
ref Task_Delegate taskListDataRef = ref MyUnSafeCommon.GetArrayDataReference(_taskList);
while ((_scheduledTaskCount > 0) && (firstEntry._cycleCount <= _cycleCounter))
{
// save the task
ScheduledTaskListEntry entry = firstEntry;
// remove it from the list
--_scheduledTaskCount;
if (_scheduledTaskCount > 0)
Array.Copy(_scheduledTaskList, 1, _scheduledTaskList, 0, _scheduledTaskCount);
// execute it
Unsafe.Add(ref taskListDataRef, entry._id)(_cycleCounter - entry._cycleCount);
}
}
public void RegisterTask(int id, Task_Delegate task)
{
_taskList[id] = task;
}
public void ScheduleTask(int id, UInt64 cycleCount)
{
// convert cycleCount from relative to absolute
cycleCount += _cycleCounter;
// get the position and reference of the new task
// (searching is done backward because a new task is more likely to be inserted towards the end)
int index = _scheduledTaskCount;
//ref ScheduledTaskListEntry entry = ref Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList), _scheduledTaskCount - 1);
ref ScheduledTaskListEntry entry = ref Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList), _scheduledTaskCount - 1);
while ((index > 0) && (entry._cycleCount > cycleCount))
{
--index;
entry = ref Unsafe.Subtract(ref entry, 1);
}
entry = ref Unsafe.Add(ref entry, 1);
// insert the new task
if (index < _scheduledTaskCount)
Array.Copy(_scheduledTaskList, index, _scheduledTaskList, index + 1, _scheduledTaskCount - index);
entry._id = id;
entry._cycleCount = cycleCount;
++_scheduledTaskCount;
}
public void CancelTask(int id)
{
int index = 0;
//ref ScheduledTaskListEntry entry = ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList);
ref ScheduledTaskListEntry entry = ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList);
while (index < _scheduledTaskCount)
{
if (entry._id == id)
{
--_scheduledTaskCount;
if (index < _scheduledTaskCount)
Array.Copy(_scheduledTaskList, index + 1, _scheduledTaskList, index, _scheduledTaskCount - index);
return;
}
++index;
entry = ref Unsafe.Add(ref entry, 1);
}
}
}
}

View File

@ -1,46 +0,0 @@
using System;
using System.IO;
namespace Iris.Common
{
public abstract class System : IDisposable
{
public delegate void PollInput_Delegate();
public delegate void PresentFrame_Delegate(UInt16[] frameBuffer);
public enum Key
{
A,
B,
Select,
Start,
Right,
Left,
Up,
Down,
R,
L,
X,
Y,
}
public enum KeyStatus
{
Input = 0,
NoInput = 1
}
public abstract void Dispose();
public abstract void ResetState(bool skipIntro);
public abstract void LoadState(BinaryReader reader);
public abstract void SaveState(BinaryWriter writer);
public abstract void LoadROM(string filename);
public abstract void SetKeyStatus(Key key, KeyStatus status);
public abstract bool IsRunning();
public abstract void Run();
public abstract void Pause();
}
}

View File

@ -1,119 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Iris.GBA
{
internal sealed class BIOS : IDisposable
{
private const int KB = 1024;
private const int BIOS_Size = 16 * KB;
private readonly IntPtr _bios = Marshal.AllocHGlobal(BIOS_Size);
private const UInt32 BIOS_StartAddress = 0x0000_0000;
private const UInt32 BIOS_EndAddress = 0x0000_4000;
private CPU.CPU_Core _cpu;
private bool _disposed;
internal BIOS()
{
Byte[] data;
try
{
data = File.ReadAllBytes("gba_bios.bin");
}
catch (FileNotFoundException)
{
throw new Exception("Iris.GBA.BIOS: Could not find BIOS");
}
catch
{
throw new Exception("Iris.GBA.BIOS: Could not read BIOS");
}
if (data.Length != BIOS_Size)
throw new Exception("Iris.GBA.BIOS: Wrong BIOS size");
Marshal.Copy(data, 0, _bios, BIOS_Size);
}
~BIOS()
{
Dispose();
}
public void Dispose()
{
if (_disposed)
return;
Marshal.FreeHGlobal(_bios);
GC.SuppressFinalize(this);
_disposed = true;
}
internal void Initialize(CPU.CPU_Core cpu, Memory memory)
{
_cpu = cpu;
memory.Map(_bios, BIOS_Size, BIOS_StartAddress, BIOS_EndAddress, Memory.Flag.AllRead);
}
internal void Reset(bool skipIntro)
{
if (skipIntro)
{
_cpu.Reg[CPU.CPU_Core.SP] = 0x300_7f00;
_cpu.Reg[CPU.CPU_Core.LR] = 0x800_0000;
_cpu.CPSR = 0x1f;
_cpu.Reg13_svc = 0x300_7fe0;
_cpu.Reg13_irq = 0x300_7fa0;
_cpu.NextInstructionAddress = 0x800_0000;
}
else
{
_cpu.CPSR = 0xd3;
_cpu.NextInstructionAddress = 0;
}
}
internal Byte Read8(UInt32 address)
{
return 0;
}
internal UInt16 Read16(UInt32 address)
{
return 0;
}
internal UInt32 Read32(UInt32 address)
{
return 0;
}
internal UInt64 HandleSWI()
{
_cpu.Reg14_svc = _cpu.NextInstructionAddress;
_cpu.SPSR_svc = _cpu.CPSR;
_cpu.SetCPSR((_cpu.CPSR & ~0xbfu) | 0x93u);
_cpu.NextInstructionAddress = 0x08;
return 3;
}
internal UInt64 HandleIRQ()
{
_cpu.Reg14_irq = _cpu.NextInstructionAddress + 4;
_cpu.SPSR_irq = _cpu.CPSR;
_cpu.SetCPSR((_cpu.CPSR & ~0xbfu) | 0x92u);
_cpu.NextInstructionAddress = 0x18;
return 3;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: a44ddbc1235a2b9448cedcd04283cb60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,250 +0,0 @@
using System;
using System.IO;
namespace Iris.GBA
{
internal sealed class Communication
{
internal enum Register
{
SIODATA0,
SIODATA1,
SIODATA2,
SIODATA3,
SIOCNT,
SIODATA_SEND,
RCNT,
JOYCNT,
JOY_RECV_L,
JOY_RECV_H,
JOY_TRANS_L,
JOY_TRANS_H,
JOYSTAT
}
private UInt16 _SIODATA0; // SIOMULTI0 / SIODATA32_L
private UInt16 _SIODATA1; // SIOMULTI1 / SIODATA32_H
private UInt16 _SIODATA2; // SIOMULTI2
private UInt16 _SIODATA3; // SIOMULTI3
private UInt16 _SIOCNT;
private UInt16 _SIODATA_SEND; // SIOMLT_SEND / SIODATA_8
private UInt16 _RCNT;
private UInt16 _JOYCNT;
private UInt16 _JOY_RECV_L;
private UInt16 _JOY_RECV_H;
private UInt16 _JOY_TRANS_L;
private UInt16 _JOY_TRANS_H;
private UInt16 _JOYSTAT;
private InterruptControl _interruptControl;
internal void Initialize(InterruptControl interruptControl)
{
_interruptControl = interruptControl;
}
internal void ResetState()
{
_SIODATA0 = 0;
_SIODATA1 = 0;
_SIODATA2 = 0;
_SIODATA3 = 0;
_SIOCNT = 0;
_SIODATA_SEND = 0;
_RCNT = 0;
_JOYCNT = 0;
_JOY_RECV_L = 0;
_JOY_RECV_H = 0;
_JOY_TRANS_L = 0;
_JOY_TRANS_H = 0;
_JOYSTAT = 0;
}
internal void LoadState(BinaryReader reader)
{
_SIODATA0 = reader.ReadUInt16();
_SIODATA1 = reader.ReadUInt16();
_SIODATA2 = reader.ReadUInt16();
_SIODATA3 = reader.ReadUInt16();
_SIOCNT = reader.ReadUInt16();
_SIODATA_SEND = reader.ReadUInt16();
_RCNT = reader.ReadUInt16();
_JOYCNT = reader.ReadUInt16();
_JOY_RECV_L = reader.ReadUInt16();
_JOY_RECV_H = reader.ReadUInt16();
_JOY_TRANS_L = reader.ReadUInt16();
_JOY_TRANS_H = reader.ReadUInt16();
_JOYSTAT = reader.ReadUInt16();
}
internal void SaveState(BinaryWriter writer)
{
writer.Write(_SIODATA0);
writer.Write(_SIODATA1);
writer.Write(_SIODATA2);
writer.Write(_SIODATA3);
writer.Write(_SIOCNT);
writer.Write(_SIODATA_SEND);
writer.Write(_RCNT);
writer.Write(_JOYCNT);
writer.Write(_JOY_RECV_L);
writer.Write(_JOY_RECV_H);
writer.Write(_JOY_TRANS_L);
writer.Write(_JOY_TRANS_H);
writer.Write(_JOYSTAT);
}
internal UInt16 ReadRegister(Register register)
{
return register switch
{
Register.SIODATA0 => _SIODATA0,
Register.SIODATA1 => _SIODATA1,
Register.SIODATA2 => _SIODATA2,
Register.SIODATA3 => _SIODATA3,
Register.SIOCNT => _SIOCNT,
Register.SIODATA_SEND => _SIODATA_SEND,
Register.RCNT => _RCNT,
Register.JOYCNT => _JOYCNT,
Register.JOY_RECV_L => _JOY_RECV_L,
Register.JOY_RECV_H => _JOY_RECV_H,
Register.JOY_TRANS_L => _JOY_TRANS_L,
Register.JOY_TRANS_H => _JOY_TRANS_H,
Register.JOYSTAT => _JOYSTAT,
// should never happen
_ => throw new Exception("Iris.GBA.Communication: Register read error"),
};
}
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
{
switch (register)
{
case Register.SIODATA0:
Memory.WriteRegisterHelper(ref _SIODATA0, value, mode);
break;
case Register.SIODATA1:
Memory.WriteRegisterHelper(ref _SIODATA1, value, mode);
break;
case Register.SIODATA2:
Memory.WriteRegisterHelper(ref _SIODATA2, value, mode);
break;
case Register.SIODATA3:
Memory.WriteRegisterHelper(ref _SIODATA3, value, mode);
break;
case Register.SIOCNT:
Memory.WriteRegisterHelper(ref _SIOCNT, value, mode);
CheckTransfer();
break;
case Register.SIODATA_SEND:
Memory.WriteRegisterHelper(ref _SIODATA_SEND, value, mode);
break;
case Register.RCNT:
Memory.WriteRegisterHelper(ref _RCNT, value, mode);
CheckTransfer();
break;
case Register.JOYCNT:
Memory.WriteRegisterHelper(ref _JOYCNT, value, mode);
break;
case Register.JOY_RECV_L:
Memory.WriteRegisterHelper(ref _JOY_RECV_L, value, mode);
break;
case Register.JOY_RECV_H:
Memory.WriteRegisterHelper(ref _JOY_RECV_H, value, mode);
break;
case Register.JOY_TRANS_L:
Memory.WriteRegisterHelper(ref _JOY_TRANS_L, value, mode);
break;
case Register.JOY_TRANS_H:
Memory.WriteRegisterHelper(ref _JOY_TRANS_H, value, mode);
break;
case Register.JOYSTAT:
Memory.WriteRegisterHelper(ref _JOYSTAT, value, mode);
break;
// should never happen
default:
throw new Exception("Iris.GBA.Communication: Register write error");
}
}
private void CheckTransfer()
{
switch ((_RCNT >> 14) & 0b11)
{
case 0b00:
case 0b01:
switch ((_SIOCNT >> 12) & 0b11)
{
case 0b00: // 8 bits normal serial communication
case 0b01: // 32 bits normal serial communication
case 0b10: // 16 bits multiplayer serial communication
if ((_SIOCNT & 0x0080) == 0x0080)
{
_SIOCNT = (UInt16)(_SIOCNT & ~0x0080);
if ((_SIOCNT & 0x4000) == 0x4000)
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.SIO);
}
break;
case 0b11:
Console.WriteLine("[Iris.GBA.Communication] UART communication not implemented");
break;
}
break;
case 0b10:
Console.WriteLine("[Iris.GBA.Communication] General purpose communication not implemented");
break;
case 0b11:
Console.WriteLine("[Iris.GBA.Communication] JOY Bus communication not implemented");
break;
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 2cb1b07716db45d45b2681382a0ff8fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,479 +0,0 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace Iris.GBA
{
internal sealed class DMA
{
internal enum Register
{
DMA0SAD_L,
DMA0SAD_H,
DMA0DAD_L,
DMA0DAD_H,
DMA0CNT_L,
DMA0CNT_H,
DMA1SAD_L,
DMA1SAD_H,
DMA1DAD_L,
DMA1DAD_H,
DMA1CNT_L,
DMA1CNT_H,
DMA2SAD_L,
DMA2SAD_H,
DMA2DAD_L,
DMA2DAD_H,
DMA2CNT_L,
DMA2CNT_H,
DMA3SAD_L,
DMA3SAD_H,
DMA3DAD_L,
DMA3DAD_H,
DMA3CNT_L,
DMA3CNT_H
}
internal enum StartTiming
{
Immediate = 0b00,
VBlank = 0b01,
HBlank = 0b10,
Special = 0b11
}
private readonly Common.Scheduler _scheduler;
private InterruptControl _interruptControl;
private Memory _memory;
private struct Channel
{
internal UInt32 _source;
internal UInt32 _sourceReload;
internal UInt32 _destination;
internal UInt32 _destinationReload;
internal UInt32 _length;
internal UInt16 _lengthReload;
internal UInt16 _control;
internal bool _running;
}
private Channel _channel0;
private Channel _channel1;
private Channel _channel2;
private Channel _channel3;
private const UInt32 MaxLengthChannel0 = 0x4000;
private const UInt32 MaxLengthChannel1 = 0x4000;
private const UInt32 MaxLengthChannel2 = 0x4000;
private const UInt32 MaxLengthChannel3 = 0x1_0000;
internal DMA(Common.Scheduler scheduler)
{
_scheduler = scheduler;
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel0, _ => Start(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0));
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel1, _ => Start(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1));
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel2, _ => Start(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2));
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel3, _ => Start(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3));
}
internal void Initialize(InterruptControl interruptControl, Memory memory)
{
_interruptControl = interruptControl;
_memory = memory;
}
internal void ResetState()
{
_channel0 = default;
_channel1 = default;
_channel2 = default;
_channel3 = default;
}
internal void LoadState(BinaryReader reader)
{
void LoadChannel(ref Channel channel)
{
channel._source = reader.ReadUInt32();
channel._sourceReload = reader.ReadUInt32();
channel._destination = reader.ReadUInt32();
channel._destinationReload = reader.ReadUInt32();
channel._length = reader.ReadUInt32();
channel._lengthReload = reader.ReadUInt16();
channel._control = reader.ReadUInt16();
channel._running = reader.ReadBoolean();
}
LoadChannel(ref _channel0);
LoadChannel(ref _channel1);
LoadChannel(ref _channel2);
LoadChannel(ref _channel3);
}
internal void SaveState(BinaryWriter writer)
{
void SaveChannel(Channel channel)
{
writer.Write(channel._source);
writer.Write(channel._sourceReload);
writer.Write(channel._destination);
writer.Write(channel._destinationReload);
writer.Write(channel._length);
writer.Write(channel._lengthReload);
writer.Write(channel._control);
writer.Write(channel._running);
}
SaveChannel(_channel0);
SaveChannel(_channel1);
SaveChannel(_channel2);
SaveChannel(_channel3);
}
internal UInt16 ReadRegister(Register register)
{
return register switch
{
Register.DMA0CNT_H => _channel0._control,
Register.DMA1CNT_H => _channel1._control,
Register.DMA2CNT_H => _channel2._control,
Register.DMA3CNT_H => _channel3._control,
// should never happen
_ => throw new Exception("Iris.GBA.DMA: Register read error"),
};
}
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
{
void WriteSourceReload_Low(ref Channel channel)
{
UInt16 low = (UInt16)channel._sourceReload;
Memory.WriteRegisterHelper(ref low, value, mode);
channel._sourceReload = (channel._sourceReload & 0xffff_0000) | low;
}
void WriteSourceReload_High(ref Channel channel, UInt16 mask)
{
UInt16 high = (UInt16)(channel._sourceReload >> 16);
Memory.WriteRegisterHelper(ref high, (UInt16)(value & mask), mode);
channel._sourceReload = (channel._sourceReload & 0x0000_ffff) | (UInt32)(high << 16);
}
void WriteDestinationReload_Low(ref Channel channel)
{
UInt16 low = (UInt16)channel._destinationReload;
Memory.WriteRegisterHelper(ref low, value, mode);
channel._destinationReload = (channel._destinationReload & 0xffff_0000) | low;
}
void WriteDestinationReload_High(ref Channel channel, UInt16 mask)
{
UInt16 high = (UInt16)(channel._destinationReload >> 16);
Memory.WriteRegisterHelper(ref high, (UInt16)(value & mask), mode);
channel._destinationReload = (channel._destinationReload & 0x0000_ffff) | (UInt32)(high << 16);
}
void WriteLengthReload(ref Channel channel)
{
UInt16 reload = channel._lengthReload;
Memory.WriteRegisterHelper(ref reload, value, mode);
channel._lengthReload = reload;
}
void WriteControl(ref Channel channel, GBA_System.TaskId startTaskId)
{
UInt16 previousControl = channel._control;
UInt16 newControl = channel._control;
Memory.WriteRegisterHelper(ref newControl, value, mode);
channel._control = newControl;
if ((previousControl & 0x8000) == 0)
{
if ((newControl & 0x8000) == 0x8000)
_scheduler.ScheduleTask((int)startTaskId, 2);
}
else
{
if ((newControl & 0x8000) == 0)
{
if (channel._running)
channel._running = false;
else
_scheduler.CancelTask((int)startTaskId);
}
}
}
switch (register)
{
case Register.DMA0SAD_L:
WriteSourceReload_Low(ref _channel0);
break;
case Register.DMA0SAD_H:
WriteSourceReload_High(ref _channel0, 0x07ff);
break;
case Register.DMA0DAD_L:
WriteDestinationReload_Low(ref _channel0);
break;
case Register.DMA0DAD_H:
WriteDestinationReload_High(ref _channel0, 0x07ff);
break;
case Register.DMA0CNT_L:
WriteLengthReload(ref _channel0);
break;
case Register.DMA0CNT_H:
WriteControl(ref _channel0, GBA_System.TaskId.StartDMA_Channel0);
break;
case Register.DMA1SAD_L:
WriteSourceReload_Low(ref _channel1);
break;
case Register.DMA1SAD_H:
WriteSourceReload_High(ref _channel1, 0x0fff);
break;
case Register.DMA1DAD_L:
WriteDestinationReload_Low(ref _channel1);
break;
case Register.DMA1DAD_H:
WriteDestinationReload_High(ref _channel1, 0x07ff);
break;
case Register.DMA1CNT_L:
WriteLengthReload(ref _channel1);
break;
case Register.DMA1CNT_H:
WriteControl(ref _channel1, GBA_System.TaskId.StartDMA_Channel1);
break;
case Register.DMA2SAD_L:
WriteSourceReload_Low(ref _channel2);
break;
case Register.DMA2SAD_H:
WriteSourceReload_High(ref _channel2, 0x0fff);
break;
case Register.DMA2DAD_L:
WriteDestinationReload_Low(ref _channel2);
break;
case Register.DMA2DAD_H:
WriteDestinationReload_High(ref _channel2, 0x07ff);
break;
case Register.DMA2CNT_L:
WriteLengthReload(ref _channel2);
break;
case Register.DMA2CNT_H:
WriteControl(ref _channel2, GBA_System.TaskId.StartDMA_Channel2);
break;
case Register.DMA3SAD_L:
WriteSourceReload_Low(ref _channel3);
break;
case Register.DMA3SAD_H:
WriteSourceReload_High(ref _channel3, 0x0fff);
break;
case Register.DMA3DAD_L:
WriteDestinationReload_Low(ref _channel3);
break;
case Register.DMA3DAD_H:
WriteDestinationReload_High(ref _channel3, 0x0fff);
break;
case Register.DMA3CNT_L:
WriteLengthReload(ref _channel3);
break;
case Register.DMA3CNT_H:
WriteControl(ref _channel3, GBA_System.TaskId.StartDMA_Channel3);
break;
// should never happen
default:
throw new Exception("Iris.GBA.DMA: Register write error");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PerformVBlankTransfers()
{
if (_channel0._running && (((_channel0._control >> 12) & 0b11) == (int)StartTiming.VBlank))
PerformTransfer(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0);
if (_channel1._running && (((_channel1._control >> 12) & 0b11) == (int)StartTiming.VBlank))
PerformTransfer(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1);
if (_channel2._running && (((_channel2._control >> 12) & 0b11) == (int)StartTiming.VBlank))
PerformTransfer(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2);
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.VBlank))
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PerformHBlankTransfers()
{
if (_channel0._running && (((_channel0._control >> 12) & 0b11) == (int)StartTiming.HBlank))
PerformTransfer(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0);
if (_channel1._running && (((_channel1._control >> 12) & 0b11) == (int)StartTiming.HBlank))
PerformTransfer(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1);
if (_channel2._running && (((_channel2._control >> 12) & 0b11) == (int)StartTiming.HBlank))
PerformTransfer(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2);
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.HBlank))
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
}
internal void PerformVideoTransfer(bool disable)
{
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.Special))
{
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
if (disable)
{
_channel3._control = (UInt16)(_channel3._control & ~0x8000);
_channel3._running = false;
}
}
}
private void Start(ref Channel channel, InterruptControl.Interrupt interrupt, UInt32 maxLength)
{
channel._source = channel._sourceReload;
channel._destination = channel._destinationReload;
channel._length = (channel._lengthReload == 0) ? maxLength : channel._lengthReload;
channel._running = true;
if (((channel._control >> 12) & 0b11) == (int)StartTiming.Immediate)
PerformTransfer(ref channel, interrupt, maxLength);
}
private void PerformTransfer(ref Channel channel, InterruptControl.Interrupt interrupt, UInt32 maxLength)
{
UInt16 sourceAddressControlFlag = (UInt16)((channel._control >> 7) & 0b11);
UInt16 destinationAddressControlFlag = (UInt16)((channel._control >> 5) & 0b11);
int GetSourceIncrement(int dataUnitSize)
{
return sourceAddressControlFlag switch
{
// increment
0b00 => dataUnitSize,
// decrement
0b01 => -dataUnitSize,
// fixed
0b10 => 0,
// prohibited
0b11 => 0,
// should never happen
_ => throw new Exception("Iris.GBA.DMA: Wrong source address control flag"),
};
}
(int destinationIncrement, bool reloadDestination) GetDestinationIncrement(int dataUnitSize)
{
return destinationAddressControlFlag switch
{
// increment
0b00 => (dataUnitSize, false),
// decrement
0b01 => (-dataUnitSize, false),
// fixed
0b10 => (0, false),
// increment+reload
0b11 => (dataUnitSize, true),
// should never happen
_ => throw new Exception("Iris.GBA.DMA: Wrong destination address control flag"),
};
}
bool reloadDestination;
// 16 bits
if ((channel._control & 0x0400) == 0)
{
const int DataUnitSize = 2;
int sourceIncrement = GetSourceIncrement(DataUnitSize);
//(int destinationIncrement, reloadDestination) = GetDestinationIncrement(DataUnitSize);
var v2 = GetDestinationIncrement(DataUnitSize);
int destinationIncrement = v2.destinationIncrement;
reloadDestination = v2.reloadDestination;
for (; channel._length > 0; --channel._length)
{
//_memory.Write16(channel._destination, _memory.Read16(channel._source));
_memory.Write16(channel._destination, _memory.Read16(channel._source));
channel._source = (UInt32)(channel._source + sourceIncrement);
channel._destination = (UInt32)(channel._destination + destinationIncrement);
_scheduler.AdvanceCycleCounter(2);
}
}
// 32 bits
else
{
const int DataUnitSize = 4;
int sourceIncrement = GetSourceIncrement(DataUnitSize);
//(int destinationIncrement, reloadDestination) = GetDestinationIncrement(DataUnitSize);
var v2 = GetDestinationIncrement(DataUnitSize);
int destinationIncrement = v2.destinationIncrement;
reloadDestination = v2.reloadDestination;
for (; channel._length > 0; --channel._length)
{
_memory.Write32(channel._destination, _memory.Read32(channel._source));
channel._source = (UInt32)(channel._source + sourceIncrement);
channel._destination = (UInt32)(channel._destination + destinationIncrement);
_scheduler.AdvanceCycleCounter(2);
}
}
if ((channel._control & 0x4000) == 0x4000)
_interruptControl.RequestInterrupt(interrupt);
// Repeat off
if ((channel._control & 0x0200) == 0)
{
channel._control = (UInt16)(channel._control & ~0x8000);
channel._running = false;
}
// Repeat on
else
{
if (reloadDestination)
channel._destination = channel._destinationReload;
channel._length = (channel._lengthReload == 0) ? maxLength : channel._lengthReload;
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 0dcd66b296ec4434b836a1075fb8a12a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,180 +0,0 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace Iris.GBA
{
public sealed class GBA_System : Common.System
{
internal enum TaskId
{
// ---- Timer ----
StartTimer_Channel0,
StartTimer_Channel1,
StartTimer_Channel2,
StartTimer_Channel3,
HandleTimerOverflow_Channel0,
HandleTimerOverflow_Channel1,
HandleTimerOverflow_Channel2,
HandleTimerOverflow_Channel3,
// ---- DMA ----
StartDMA_Channel0,
StartDMA_Channel1,
StartDMA_Channel2,
StartDMA_Channel3,
// ---- KeyInput ----
CheckKeyInterrupt,
// ---- Video ----
StartHBlank,
StartScanline
}
private static readonly int s_taskIdCount = Enum.GetNames(typeof(TaskId)).Length;
private readonly Common.Scheduler _scheduler = new(s_taskIdCount, 2 * s_taskIdCount);
private readonly CPU.CPU_Core _cpu;
private readonly Communication _communication = new();
private readonly Timer _timer;
private readonly Sound _sound = new();
private readonly DMA _dma;
private readonly KeyInput _keyInput;
private readonly SystemControl _systemControl = new();
private readonly InterruptControl _interruptControl = new();
private readonly Memory _memory = new();
private readonly Video _video;
private readonly BIOS _bios = new();
private string _romHash;
private bool _running;
private const string StateSaveMagic = "IRISGBA";
private const int StateSaveVersion = 1;
public GBA_System(PollInput_Delegate pollInputCallback, PresentFrame_Delegate presentFrameCallback)
{
CPU.CPU_Core.CallbackInterface cpuCallbackInterface = new(_memory.Read8, _memory.Read16, _memory.Read32, _memory.Write8, _memory.Write16, _memory.Write32, _bios.HandleSWI, _bios.HandleIRQ);
_cpu = new(CPU.CPU_Core.Model.ARM7TDMI, cpuCallbackInterface);
_timer = new(_scheduler);
_dma = new(_scheduler);
_keyInput = new(_scheduler, pollInputCallback);
_video = new(_scheduler, presentFrameCallback);
_communication.Initialize(_interruptControl);
_timer.Initialize(_interruptControl);
_dma.Initialize(_interruptControl, _memory);
_keyInput.Initialize(_interruptControl);
_interruptControl.Initialize(_cpu);
_memory.Initialize(_communication, _timer, _sound, _dma, _keyInput, _systemControl, _interruptControl, _video, _bios);
_video.Initialize(_dma, _interruptControl, _memory);
_bios.Initialize(_cpu, _memory);
}
public override void Dispose()
{
_memory.Dispose();
_video.Dispose();
_bios.Dispose();
}
public override void ResetState(bool skipIntro)
{
_scheduler.ResetState(); // This has to be done first
_cpu.ResetState();
_communication.ResetState();
_timer.ResetState();
_sound.ResetState();
_dma.ResetState();
_keyInput.ResetState();
_systemControl.ResetState();
_interruptControl.ResetState();
_memory.ResetState();
_video.ResetState();
_bios.Reset(skipIntro); // This has to be done last
}
public override void LoadState(BinaryReader reader)
{
if (reader.ReadString() != StateSaveMagic)
throw new Exception("Iris.GBA.GBA_System: Wrong state save magic");
if (reader.ReadInt32() != StateSaveVersion)
throw new Exception("Iris.GBA.GBA_System: Wrong state save version");
if (reader.ReadString() != _romHash)
throw new Exception("Iris.GBA.GBA_System: Wrong ROM hash");
_scheduler.LoadState(reader);
_cpu.LoadState(reader);
_communication.LoadState(reader);
_timer.LoadState(reader);
_sound.LoadState(reader);
_dma.LoadState(reader);
_keyInput.LoadState(reader);
_systemControl.LoadState(reader);
_interruptControl.LoadState(reader);
_memory.LoadState(reader);
_video.LoadState(reader);
}
public override void SaveState(BinaryWriter writer)
{
writer.Write(StateSaveMagic);
writer.Write(StateSaveVersion);
writer.Write(_romHash);
_scheduler.SaveState(writer);
_cpu.SaveState(writer);
_communication.SaveState(writer);
_timer.SaveState(writer);
_sound.SaveState(writer);
_dma.SaveState(writer);
_keyInput.SaveState(writer);
_systemControl.SaveState(writer);
_interruptControl.SaveState(writer);
_memory.SaveState(writer);
_video.SaveState(writer);
}
public override void LoadROM(string filename)
{
_memory.LoadROM(filename);
using HashAlgorithm hashAlgorithm = SHA512.Create();
using FileStream fileStream = File.OpenRead(filename);
_romHash = BitConverter.ToString(hashAlgorithm.ComputeHash(fileStream));
}
public override void SetKeyStatus(Key key, KeyStatus status)
{
_keyInput.SetKeyStatus(key, status);
}
public override bool IsRunning()
{
return _running;
}
public override void Run()
{
_running = true;
while (_running)
{
UInt64 cycleCount = _cpu.Step();
_scheduler.AdvanceCycleCounter(cycleCount);
}
}
public override void Pause()
{
_running = false;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7bc3a1e87e31e9a4abef36e543dab2c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,127 +0,0 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace Iris.GBA
{
internal sealed class InterruptControl
{
internal enum Register
{
IE,
IF,
IME
}
internal enum Interrupt
{
VBlank = 1 << 0,
HBlank = 1 << 1,
VCountMatch = 1 << 2,
Timer0 = 1 << 3,
Timer1 = 1 << 4,
Timer2 = 1 << 5,
Timer3 = 1 << 6,
SIO = 1 << 7,
DMA0 = 1 << 8,
DMA1 = 1 << 9,
DMA2 = 1 << 10,
DMA3 = 1 << 11,
Key = 1 << 12,
//GamePak = 1 << 13
}
private UInt16 _IE;
private UInt16 _IF;
private UInt16 _IME;
private CPU.CPU_Core _cpu;
internal void Initialize(CPU.CPU_Core cpu)
{
_cpu = cpu;
}
internal void ResetState()
{
_IE = 0;
_IF = 0;
_IME = 0;
}
internal void LoadState(BinaryReader reader)
{
_IE = reader.ReadUInt16();
_IF = reader.ReadUInt16();
_IME = reader.ReadUInt16();
}
internal void SaveState(BinaryWriter writer)
{
writer.Write(_IE);
writer.Write(_IF);
writer.Write(_IME);
}
internal UInt16 ReadRegister(Register register)
{
return register switch
{
Register.IE => _IE,
Register.IF => _IF,
Register.IME => _IME,
// should never happen
_ => throw new Exception("Iris.GBA.InterruptControl: Register read error"),
};
}
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
{
switch (register)
{
case Register.IE:
Memory.WriteRegisterHelper(ref _IE, value, mode);
break;
case Register.IF:
switch (mode)
{
case Memory.RegisterWriteMode.LowByte:
_IF &= (UInt16)~value;
break;
case Memory.RegisterWriteMode.HighByte:
_IF &= (UInt16)~(value << 8);
break;
case Memory.RegisterWriteMode.HalfWord:
_IF &= (UInt16)~value;
break;
}
break;
case Register.IME:
Memory.WriteRegisterHelper(ref _IME, value, mode);
break;
// should never happen
default:
throw new Exception("Iris.GBA.InterruptControl: Register write error");
}
CheckInterrupts();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RequestInterrupt(Interrupt interrupt)
{
_IF |= (UInt16)interrupt;
CheckInterrupts();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckInterrupts()
{
_cpu.NIRQ = ((_IME == 0) || ((_IE & _IF) == 0)) ? CPU.CPU_Core.Signal.High : CPU.CPU_Core.Signal.Low;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c58052e2ebb4549418cb2d58b101d5ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,179 +0,0 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace Iris.GBA
{
internal sealed class KeyInput
{
internal enum Register
{
KEYINPUT,
KEYCNT
}
private UInt16 _KEYINPUT;
private UInt16 _KEYCNT;
private readonly Common.Scheduler _scheduler;
private readonly Common.System.PollInput_Delegate _pollInputCallback;
private InterruptControl _interruptControl;
private const UInt64 CheckInterruptCycleCount = 280_896; // once per frame
private bool _checkingInterrupt;
internal KeyInput(Common.Scheduler scheduler, Common.System.PollInput_Delegate pollInputCallback)
{
_scheduler = scheduler;
_pollInputCallback = pollInputCallback;
_scheduler.RegisterTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterrupt);
}
internal void Initialize(InterruptControl interruptControl)
{
_interruptControl = interruptControl;
}
internal void ResetState()
{
_KEYINPUT = 0x03ff;
_KEYCNT = 0;
}
internal void LoadState(BinaryReader reader)
{
_KEYINPUT = reader.ReadUInt16();
_KEYCNT = reader.ReadUInt16();
}
internal void SaveState(BinaryWriter writer)
{
writer.Write(_KEYINPUT);
writer.Write(_KEYCNT);
}
internal UInt16 ReadRegister(Register register)
{
switch (register)
{
case Register.KEYINPUT:
_pollInputCallback();
if ((_KEYCNT & 0x4000) == 0x4000)
CheckInterrupt();
return _KEYINPUT;
case Register.KEYCNT:
return _KEYCNT;
// should never happen
default:
throw new Exception("Iris.GBA.KeyInput: Register read error");
}
}
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
{
switch (register)
{
case Register.KEYCNT:
Memory.WriteRegisterHelper(ref _KEYCNT, value, mode);
if ((_KEYCNT & 0x4000) == 0x4000)
{
_pollInputCallback();
CheckInterrupt();
if (!_checkingInterrupt)
{
_checkingInterrupt = true;
_scheduler.ScheduleTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterruptCycleCount);
}
}
break;
// should never happen
default:
throw new Exception("Iris.GBA.KeyInput: Register write error");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SetKeyStatus(Common.System.Key key, Common.System.KeyStatus status)
{
int pos;
switch (key)
{
case Common.System.Key.A:
pos = 0;
break;
case Common.System.Key.B:
pos = 1;
break;
case Common.System.Key.Select:
pos = 2;
break;
case Common.System.Key.Start:
pos = 3;
break;
case Common.System.Key.Right:
pos = 4;
break;
case Common.System.Key.Left:
pos = 5;
break;
case Common.System.Key.Up:
pos = 6;
break;
case Common.System.Key.Down:
pos = 7;
break;
case Common.System.Key.R:
pos = 8;
break;
case Common.System.Key.L:
pos = 9;
break;
default:
return;
}
_KEYINPUT = (UInt16)((_KEYINPUT & ~(1 << pos)) | ((int)status << pos));
}
private void CheckInterrupt(UInt64 cycleCountDelay)
{
if ((_KEYCNT & 0x4000) == 0)
{
_checkingInterrupt = false;
return;
}
_pollInputCallback();
CheckInterrupt();
_scheduler.ScheduleTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterruptCycleCount - cycleCountDelay);
}
private void CheckInterrupt()
{
UInt16 mask = (UInt16)(_KEYCNT & 0x03ff);
if ((_KEYCNT & 0x8000) == 0)
{
if ((~_KEYINPUT & mask) != 0)
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.Key);
}
else
{
// TODO: figure out what happens when mask == 0
if ((~_KEYINPUT & mask) == mask)
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.Key);
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: ee2423bcb9113dc4580cb958b890e91d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -1,351 +0,0 @@
using System;
using System.IO;
namespace Iris.GBA
{
internal sealed class Sound
{
internal enum Register
{
SOUND1CNT_L,
SOUND1CNT_H,
SOUND1CNT_X,
SOUND2CNT_L,
SOUND2CNT_H,
SOUND3CNT_L,
SOUND3CNT_H,
SOUND3CNT_X,
SOUND4CNT_L,
SOUND4CNT_H,
SOUNDCNT_L,
SOUNDCNT_H,
SOUNDCNT_X,
SOUNDBIAS,
WAVE_RAM0_L,
WAVE_RAM0_H,
WAVE_RAM1_L,
WAVE_RAM1_H,
WAVE_RAM2_L,
WAVE_RAM2_H,
WAVE_RAM3_L,
WAVE_RAM3_H,
FIFO_A_L,
FIFO_A_H,
FIFO_B_L,
FIFO_B_H
}
private UInt16 _SOUND1CNT_L;
private UInt16 _SOUND1CNT_H;
private UInt16 _SOUND1CNT_X;
private UInt16 _SOUND2CNT_L;
private UInt16 _SOUND2CNT_H;
private UInt16 _SOUND3CNT_L;
private UInt16 _SOUND3CNT_H;
private UInt16 _SOUND3CNT_X;
private UInt16 _SOUND4CNT_L;
private UInt16 _SOUND4CNT_H;
private UInt16 _SOUNDCNT_L;
private UInt16 _SOUNDCNT_H;
private UInt16 _SOUNDCNT_X;
private UInt16 _SOUNDBIAS;
private UInt16 _WAVE_RAM0_L;
private UInt16 _WAVE_RAM0_H;
private UInt16 _WAVE_RAM1_L;
private UInt16 _WAVE_RAM1_H;
private UInt16 _WAVE_RAM2_L;
private UInt16 _WAVE_RAM2_H;
private UInt16 _WAVE_RAM3_L;
private UInt16 _WAVE_RAM3_H;
private UInt16 _FIFO_A_L;
private UInt16 _FIFO_A_H;
private UInt16 _FIFO_B_L;
private UInt16 _FIFO_B_H;
internal void ResetState()
{
_SOUND1CNT_L = 0;
_SOUND1CNT_H = 0;
_SOUND1CNT_X = 0;
_SOUND2CNT_L = 0;
_SOUND2CNT_H = 0;
_SOUND3CNT_L = 0;
_SOUND3CNT_H = 0;
_SOUND3CNT_X = 0;
_SOUND4CNT_L = 0;
_SOUND4CNT_H = 0;
_SOUNDCNT_L = 0;
_SOUNDCNT_H = 0;
_SOUNDCNT_X = 0;
_SOUNDBIAS = 0;
_WAVE_RAM0_L = 0;
_WAVE_RAM0_H = 0;
_WAVE_RAM1_L = 0;
_WAVE_RAM1_H = 0;
_WAVE_RAM2_L = 0;
_WAVE_RAM2_H = 0;
_WAVE_RAM3_L = 0;
_WAVE_RAM3_H = 0;
_FIFO_A_L = 0;
_FIFO_A_H = 0;
_FIFO_B_L = 0;
_FIFO_B_H = 0;
}
internal void LoadState(BinaryReader reader)
{
_SOUND1CNT_L = reader.ReadUInt16();
_SOUND1CNT_H = reader.ReadUInt16();
_SOUND1CNT_X = reader.ReadUInt16();
_SOUND2CNT_L = reader.ReadUInt16();
_SOUND2CNT_H = reader.ReadUInt16();
_SOUND3CNT_L = reader.ReadUInt16();
_SOUND3CNT_H = reader.ReadUInt16();
_SOUND3CNT_X = reader.ReadUInt16();
_SOUND4CNT_L = reader.ReadUInt16();
_SOUND4CNT_H = reader.ReadUInt16();
_SOUNDCNT_L = reader.ReadUInt16();
_SOUNDCNT_H = reader.ReadUInt16();
_SOUNDCNT_X = reader.ReadUInt16();
_SOUNDBIAS = reader.ReadUInt16();
_WAVE_RAM0_L = reader.ReadUInt16();
_WAVE_RAM0_H = reader.ReadUInt16();
_WAVE_RAM1_L = reader.ReadUInt16();
_WAVE_RAM1_H = reader.ReadUInt16();
_WAVE_RAM2_L = reader.ReadUInt16();
_WAVE_RAM2_H = reader.ReadUInt16();
_WAVE_RAM3_L = reader.ReadUInt16();
_WAVE_RAM3_H = reader.ReadUInt16();
_FIFO_A_L = reader.ReadUInt16();
_FIFO_A_H = reader.ReadUInt16();
_FIFO_B_L = reader.ReadUInt16();
_FIFO_B_H = reader.ReadUInt16();
}
internal void SaveState(BinaryWriter writer)
{
writer.Write(_SOUND1CNT_L);
writer.Write(_SOUND1CNT_H);
writer.Write(_SOUND1CNT_X);
writer.Write(_SOUND2CNT_L);
writer.Write(_SOUND2CNT_H);
writer.Write(_SOUND3CNT_L);
writer.Write(_SOUND3CNT_H);
writer.Write(_SOUND3CNT_X);
writer.Write(_SOUND4CNT_L);
writer.Write(_SOUND4CNT_H);
writer.Write(_SOUNDCNT_L);
writer.Write(_SOUNDCNT_H);
writer.Write(_SOUNDCNT_X);
writer.Write(_SOUNDBIAS);
writer.Write(_WAVE_RAM0_L);
writer.Write(_WAVE_RAM0_H);
writer.Write(_WAVE_RAM1_L);
writer.Write(_WAVE_RAM1_H);
writer.Write(_WAVE_RAM2_L);
writer.Write(_WAVE_RAM2_H);
writer.Write(_WAVE_RAM3_L);
writer.Write(_WAVE_RAM3_H);
writer.Write(_FIFO_A_L);
writer.Write(_FIFO_A_H);
writer.Write(_FIFO_B_L);
writer.Write(_FIFO_B_H);
}
internal UInt16 ReadRegister(Register register)
{
return register switch
{
Register.SOUND1CNT_L => _SOUND1CNT_L,
Register.SOUND1CNT_H => _SOUND1CNT_H,
Register.SOUND1CNT_X => _SOUND1CNT_X,
Register.SOUND2CNT_L => _SOUND2CNT_L,
Register.SOUND2CNT_H => _SOUND2CNT_H,
Register.SOUND3CNT_L => _SOUND3CNT_L,
Register.SOUND3CNT_H => _SOUND3CNT_H,
Register.SOUND3CNT_X => _SOUND3CNT_X,
Register.SOUND4CNT_L => _SOUND4CNT_L,
Register.SOUND4CNT_H => _SOUND4CNT_H,
Register.SOUNDCNT_L => _SOUNDCNT_L,
Register.SOUNDCNT_H => _SOUNDCNT_H,
Register.SOUNDCNT_X => _SOUNDCNT_X,
Register.SOUNDBIAS => _SOUNDBIAS,
Register.WAVE_RAM0_L => _WAVE_RAM0_L,
Register.WAVE_RAM0_H => _WAVE_RAM0_H,
Register.WAVE_RAM1_L => _WAVE_RAM1_L,
Register.WAVE_RAM1_H => _WAVE_RAM1_H,
Register.WAVE_RAM2_L => _WAVE_RAM2_L,
Register.WAVE_RAM2_H => _WAVE_RAM2_H,
Register.WAVE_RAM3_L => _WAVE_RAM3_L,
Register.WAVE_RAM3_H => _WAVE_RAM3_H,
// should never happen
_ => throw new Exception("Iris.GBA.Sound: Register read error"),
};
}
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
{
switch (register)
{
case Register.SOUND1CNT_L:
Memory.WriteRegisterHelper(ref _SOUND1CNT_L, value, mode);
break;
case Register.SOUND1CNT_H:
Memory.WriteRegisterHelper(ref _SOUND1CNT_H, value, mode);
break;
case Register.SOUND1CNT_X:
Memory.WriteRegisterHelper(ref _SOUND1CNT_X, value, mode);
break;
case Register.SOUND2CNT_L:
Memory.WriteRegisterHelper(ref _SOUND2CNT_L, value, mode);
break;
case Register.SOUND2CNT_H:
Memory.WriteRegisterHelper(ref _SOUND2CNT_H, value, mode);
break;
case Register.SOUND3CNT_L:
Memory.WriteRegisterHelper(ref _SOUND3CNT_L, value, mode);
break;
case Register.SOUND3CNT_H:
Memory.WriteRegisterHelper(ref _SOUND3CNT_H, value, mode);
break;
case Register.SOUND3CNT_X:
Memory.WriteRegisterHelper(ref _SOUND3CNT_X, value, mode);
break;
case Register.SOUND4CNT_L:
Memory.WriteRegisterHelper(ref _SOUND4CNT_L, value, mode);
break;
case Register.SOUND4CNT_H:
Memory.WriteRegisterHelper(ref _SOUND4CNT_H, value, mode);
break;
case Register.SOUNDCNT_L:
Memory.WriteRegisterHelper(ref _SOUNDCNT_L, value, mode);
break;
case Register.SOUNDCNT_H:
Memory.WriteRegisterHelper(ref _SOUNDCNT_H, value, mode);
break;
case Register.SOUNDCNT_X:
Memory.WriteRegisterHelper(ref _SOUNDCNT_X, value, mode);
break;
case Register.SOUNDBIAS:
Memory.WriteRegisterHelper(ref _SOUNDBIAS, value, mode);
break;
case Register.WAVE_RAM0_L:
Memory.WriteRegisterHelper(ref _WAVE_RAM0_L, value, mode);
break;
case Register.WAVE_RAM0_H:
Memory.WriteRegisterHelper(ref _WAVE_RAM0_H, value, mode);
break;
case Register.WAVE_RAM1_L:
Memory.WriteRegisterHelper(ref _WAVE_RAM1_L, value, mode);
break;
case Register.WAVE_RAM1_H:
Memory.WriteRegisterHelper(ref _WAVE_RAM1_H, value, mode);
break;
case Register.WAVE_RAM2_L:
Memory.WriteRegisterHelper(ref _WAVE_RAM2_L, value, mode);
break;
case Register.WAVE_RAM2_H:
Memory.WriteRegisterHelper(ref _WAVE_RAM2_H, value, mode);
break;
case Register.WAVE_RAM3_L:
Memory.WriteRegisterHelper(ref _WAVE_RAM3_L, value, mode);
break;
case Register.WAVE_RAM3_H:
Memory.WriteRegisterHelper(ref _WAVE_RAM3_H, value, mode);
break;
case Register.FIFO_A_L:
Memory.WriteRegisterHelper(ref _FIFO_A_L, value, mode);
break;
case Register.FIFO_A_H:
Memory.WriteRegisterHelper(ref _FIFO_A_H, value, mode);
break;
case Register.FIFO_B_L:
Memory.WriteRegisterHelper(ref _FIFO_B_L, value, mode);
break;
case Register.FIFO_B_H:
Memory.WriteRegisterHelper(ref _FIFO_B_H, value, mode);
break;
// should never happen
default:
throw new Exception("Iris.GBA.Sound: Register write error");
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 6e76b919919204d4db34341937e30bc9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,64 +0,0 @@
using System;
using System.IO;
namespace Iris.GBA
{
internal sealed class SystemControl
{
internal enum Register
{
WAITCNT,
SYSCNT_UND0 // undocumented - Post Boot Flag (POSTFLG) & Power Down Control (HALTCNT)
}
private UInt16 _WAITCNT;
private UInt16 _SYSCNT_UND0;
internal void ResetState()
{
_WAITCNT = 0;
_SYSCNT_UND0 = 0;
}
internal void LoadState(BinaryReader reader)
{
_WAITCNT = reader.ReadUInt16();
_SYSCNT_UND0 = reader.ReadUInt16();
}
internal void SaveState(BinaryWriter writer)
{
writer.Write(_WAITCNT);
writer.Write(_SYSCNT_UND0);
}
internal UInt16 ReadRegister(Register register)
{
return register switch
{
Register.WAITCNT => _WAITCNT,
Register.SYSCNT_UND0 => _SYSCNT_UND0,
// should never happen
_ => throw new Exception("Iris.GBA.SystemControl: Register read error"),
};
}
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
{
switch (register)
{
case Register.WAITCNT:
Memory.WriteRegisterHelper(ref _WAITCNT, value, mode);
break;
case Register.SYSCNT_UND0:
Memory.WriteRegisterHelper(ref _SYSCNT_UND0, value, mode);
break;
// should never happen
default:
throw new Exception("Iris.GBA.SystemControl: Register write error");
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 24d9a1588f50c654691f136b394d3d31
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,383 +0,0 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using static UnityEditor.Experimental.AssetDatabaseExperimental.AssetDatabaseCounters;
namespace Iris.GBA
{
internal sealed class Timer
{
internal enum Register
{
TM0CNT_L,
TM0CNT_H,
TM1CNT_L,
TM1CNT_H,
TM2CNT_L,
TM2CNT_H,
TM3CNT_L,
TM3CNT_H
}
private readonly Common.Scheduler _scheduler;
private InterruptControl _interruptControl;
//private struct Channel(GBA_System.TaskId startTaskId, GBA_System.TaskId handleOverflowTaskId, InterruptControl.Interrupt interrupt)
//{
// internal UInt16 _counter;
// internal UInt16 _reload;
// internal UInt16 _control;
// internal UInt64 _cycleCount; // only used in non-cascading mode
// internal bool _running;
// internal readonly GBA_System.TaskId _startTaskId = startTaskId;
// internal readonly GBA_System.TaskId _handleOverflowTaskId = handleOverflowTaskId;
// internal readonly InterruptControl.Interrupt _interrupt = interrupt;
//}
private struct Channel
{
internal Channel(GBA_System.TaskId startTaskId, GBA_System.TaskId handleOverflowTaskId, InterruptControl.Interrupt interrupt)
{
_startTaskId = startTaskId;
_handleOverflowTaskId = handleOverflowTaskId;
_interrupt = interrupt;
_counter = 0;
_reload = 0;
_control = 0;
_cycleCount = 0;
_running = false;
}
internal UInt16 _counter;
internal UInt16 _reload;
internal UInt16 _control;
internal UInt64 _cycleCount; // only used in non-cascading mode
internal bool _running;
internal readonly GBA_System.TaskId _startTaskId;
internal readonly GBA_System.TaskId _handleOverflowTaskId;
internal readonly InterruptControl.Interrupt _interrupt;
}
private readonly Channel[] _channels;
internal Timer(Common.Scheduler scheduler)
{
_scheduler = scheduler;
_channels = new Channel[]
{
new Channel(GBA_System.TaskId.StartTimer_Channel0, GBA_System.TaskId.HandleTimerOverflow_Channel0, InterruptControl.Interrupt.Timer0),
new Channel(GBA_System.TaskId.StartTimer_Channel1, GBA_System.TaskId.HandleTimerOverflow_Channel1, InterruptControl.Interrupt.Timer1),
new Channel(GBA_System.TaskId.StartTimer_Channel2, GBA_System.TaskId.HandleTimerOverflow_Channel2, InterruptControl.Interrupt.Timer2),
new Channel(GBA_System.TaskId.StartTimer_Channel3, GBA_System.TaskId.HandleTimerOverflow_Channel3, InterruptControl.Interrupt.Timer3)
};
for (int channelIndex = 0; channelIndex < 4; ++channelIndex)
{
int channelIndexCopy = channelIndex;
_scheduler.RegisterTask((int)_channels[channelIndex]._startTaskId, cycleCountDelay => Start(channelIndexCopy, cycleCountDelay));
_scheduler.RegisterTask((int)_channels[channelIndex]._handleOverflowTaskId, cycleCountDelay => HandleOverflow(channelIndexCopy, cycleCountDelay));
}
}
internal void Initialize(InterruptControl interruptControl)
{
_interruptControl = interruptControl;
}
internal void ResetState()
{
foreach (ref Channel channel in _channels.AsSpan())
{
channel._counter = 0;
channel._reload = 0;
channel._control = 0;
channel._cycleCount = 0;
channel._running = false;
}
}
internal void LoadState(BinaryReader reader)
{
foreach (ref Channel channel in _channels.AsSpan())
{
channel._counter = reader.ReadUInt16();
channel._reload = reader.ReadUInt16();
channel._control = reader.ReadUInt16();
channel._cycleCount = reader.ReadUInt64();
channel._running = reader.ReadBoolean();
}
}
internal void SaveState(BinaryWriter writer)
{
foreach (Channel channel in _channels)
{
writer.Write(channel._counter);
writer.Write(channel._reload);
writer.Write(channel._control);
writer.Write(channel._cycleCount);
writer.Write(channel._running);
}
}
internal UInt16 ReadRegister(Register register)
{
UInt16 ReadCounter(int channelIndex)
{
ref Channel channel = ref _channels[channelIndex];
if (channel._running && (((channel._control & 0x0004) == 0) || (channelIndex == 0)))
UpdateCounter(ref channel, channel._control);
return channel._counter;
}
return register switch
{
Register.TM0CNT_L => ReadCounter(0),
Register.TM0CNT_H => _channels[0]._control,
Register.TM1CNT_L => ReadCounter(1),
Register.TM1CNT_H => _channels[1]._control,
Register.TM2CNT_L => ReadCounter(2),
Register.TM2CNT_H => _channels[2]._control,
Register.TM3CNT_L => ReadCounter(3),
Register.TM3CNT_H => _channels[3]._control,
// should never happen
_ => throw new Exception("Iris.GBA.Timer: Register read error"),
};
}
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
{
void WriteReload(ref Channel channel)
{
UInt16 reload = channel._reload;
Memory.WriteRegisterHelper(ref reload, value, mode);
channel._reload = reload;
}
void WriteControl(int channelIndex)
{
ref Channel channel = ref _channels[channelIndex];
UInt16 previousControl = channel._control;
UInt16 newControl = channel._control;
Memory.WriteRegisterHelper(ref newControl, value, mode);
channel._control = newControl;
CheckControl(ref channel, channelIndex, previousControl, newControl);
}
switch (register)
{
case Register.TM0CNT_L:
WriteReload(ref _channels[0]);
break;
case Register.TM0CNT_H:
WriteControl(0);
break;
case Register.TM1CNT_L:
WriteReload(ref _channels[1]);
break;
case Register.TM1CNT_H:
WriteControl(1);
break;
case Register.TM2CNT_L:
WriteReload(ref _channels[2]);
break;
case Register.TM2CNT_H:
WriteControl(2);
break;
case Register.TM3CNT_L:
WriteReload(ref _channels[3]);
break;
case Register.TM3CNT_H:
WriteControl(3);
break;
// should never happen
default:
throw new Exception("Iris.GBA.Timer: Register write error");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckControl(ref Channel channel, int channelIndex, UInt16 previousControl, UInt16 newControl)
{
if ((previousControl & 0x0080) == 0)
{
if ((newControl & 0x0080) == 0x0080)
_scheduler.ScheduleTask((int)channel._startTaskId, 2);
}
else
{
if ((newControl & 0x0080) == 0)
{
if (channel._running)
{
if (((previousControl & 0x0004) == 0) || (channelIndex == 0))
{
UpdateCounter(ref channel, previousControl);
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
}
channel._running = false;
}
else
{
_scheduler.CancelTask((int)channel._startTaskId);
}
}
else
{
if (channel._running)
{
if (channelIndex == 0)
{
if ((previousControl & 0b11) != (newControl & 0b11))
{
UpdateCounter(ref channel, previousControl);
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
}
}
else
{
if ((previousControl & 0x0004) == 0)
{
if ((newControl & 0x0004) == 0)
{
if ((previousControl & 0b11) != (newControl & 0b11))
{
UpdateCounter(ref channel, previousControl);
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
}
}
else
{
UpdateCounter(ref channel, previousControl);
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
}
}
else
{
if ((newControl & 0x0004) == 0)
{
channel._cycleCount = _scheduler.GetCycleCounter();
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
}
}
}
}
}
}
}
private void UpdateCounter(ref Channel channel, UInt16 control)
{
UInt64 currentCycleCount = _scheduler.GetCycleCounter();
UInt64 cycleCountDelta = currentCycleCount - channel._cycleCount;
UInt64 prescaler = GetPrescaler(control);
channel._counter += (UInt16)(cycleCountDelta / prescaler);
channel._cycleCount = currentCycleCount - (UInt16)(cycleCountDelta % prescaler);
}
private void Start(int channelIndex, UInt64 cycleCountDelay)
{
ref Channel channel = ref _channels[channelIndex];
channel._counter = channel._reload;
channel._running = true;
if (((channel._control & 0x0004) == 0) || (channelIndex == 0))
{
channel._cycleCount = _scheduler.GetCycleCounter() - cycleCountDelay;
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel) - cycleCountDelay);
}
}
private void HandleOverflow(int channelIndex, UInt64 cycleCountDelay)
{
ref Channel channel = ref _channels[channelIndex];
channel._counter = channel._reload;
channel._cycleCount = _scheduler.GetCycleCounter() - cycleCountDelay;
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel) - cycleCountDelay);
if ((channel._control & 0x0040) == 0x0040)
_interruptControl.RequestInterrupt(channel._interrupt);
CascadeOverflow(channelIndex);
}
private void CascadeOverflow(int channelIndex)
{
if (channelIndex == 3)
return;
++channelIndex;
ref Channel channel = ref _channels[channelIndex];
if (!channel._running || ((channel._control & 0x0004) != 0x0004))
return;
if (channel._counter == 0xffff)
{
channel._counter = channel._reload;
if ((channel._control & 0x0040) == 0x0040)
_interruptControl.RequestInterrupt(channel._interrupt);
CascadeOverflow(channelIndex);
}
else
{
++channel._counter;
}
}
//private static UInt64 ComputeCycleCountUntilOverflow(ref readonly Channel channel)
private static UInt64 ComputeCycleCountUntilOverflow(ref Channel channel)
{
return (0x1_0000u - channel._counter) * GetPrescaler(channel._control);
}
private static UInt64 GetPrescaler(UInt16 control)
{
return (control & 0b11) switch
{
0b00 => 1,
0b01 => 64,
0b10 => 256,
0b11 => 1024,
_ => 0, // cannot happen
};
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c1c87077f94b1c547b5ef23240dca9cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c10b6bffb02e139458b969dbf72fc69b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 89f540181c8a126469d24bee76e92113
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,27 +0,0 @@
using System;
namespace Iris.NDS
{
public sealed partial class NDS_System
{
private void BIOS_Reset()
{
const UInt32 ROMAddress = 0x0800_0000;
// TODO
_cpu.Reg[CPU.CPU_Core.PC] = ROMAddress;
_cpu.NextInstructionAddress = ROMAddress;
}
private UInt64 HandleSWI()
{
throw new NotImplementedException("Iris.NDS.Core.BIOS: HandleSWI unimplemented");
}
private UInt64 HandleIRQ()
{
throw new NotImplementedException("Iris.NDS.Core.BIOS: HandleIRQ unimplemented");
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 65bfe93914138fb498b88080b1e38337
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,47 +0,0 @@
using System;
using System.IO;
namespace Iris.NDS
{
public sealed partial class NDS_System
{
private const int KB = 1024;
private Byte[]? _ROM;
public override void LoadROM(string filename)
{
_ROM = File.ReadAllBytes(filename);
}
private Byte ReadMemory8(UInt32 address)
{
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory8 unimplemented");
}
private UInt16 ReadMemory16(UInt32 address)
{
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory16 unimplemented");
}
private UInt32 ReadMemory32(UInt32 address)
{
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented");
}
private void WriteMemory8(UInt32 address, Byte value)
{
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented");
}
private void WriteMemory16(UInt32 address, UInt16 value)
{
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented");
}
private void WriteMemory32(UInt32 address, UInt32 value)
{
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented");
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 901a653d16f65ca4b906d7d0fb660a03
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,76 +0,0 @@
using System;
using System.IO;
namespace Iris.NDS
{
public sealed partial class NDS_System : Common.System
{
private readonly CPU.CPU_Core _cpu;
private readonly PPU _ppu;
private bool _running;
private bool _disposed;
public NDS_System(PollInput_Delegate pollInputCallback, PresentFrame_Delegate presentFrameCallback)
{
CPU.CPU_Core.CallbackInterface cpuCallbackInterface = new(ReadMemory8, ReadMemory16, ReadMemory32, WriteMemory8, WriteMemory16, WriteMemory32, HandleSWI, HandleIRQ);
_cpu = new(CPU.CPU_Core.Model.ARM946ES, cpuCallbackInterface);
_ppu = new(presentFrameCallback);
}
public override void Dispose()
{
if (_disposed)
return;
// TODO
_disposed = true;
}
public override void ResetState(bool skipIntro)
{
BIOS_Reset();
_cpu.NIRQ = CPU.CPU_Core.Signal.High;
}
public override void LoadState(BinaryReader reader)
{
// TODO
}
public override void SaveState(BinaryWriter writer)
{
// TODO
}
public override bool IsRunning()
{
return _running;
}
public override void Run()
{
_running = true;
while (_running)
{
UInt64 cycles = _cpu.Step();
for (UInt64 i = 0; i < cycles; ++i)
_ppu.Step();
}
}
public override void Pause()
{
_running = false;
}
public override void SetKeyStatus(Key key, KeyStatus status)
{
// TODO
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 2233aca5b62bca346b553db0b0fcc482
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,19 +0,0 @@
namespace Iris.NDS
{
public sealed class PPU
{
private const int KB = 1024;
private readonly Common.System.PresentFrame_Delegate _presentFrameCallback;
internal PPU(Common.System.PresentFrame_Delegate presentFrameCallback)
{
_presentFrameCallback = presentFrameCallback;
}
internal void Step()
{
// TODO
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: fd011144397045948876bf23745e4a4c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,91 +0,0 @@
using System;
public static class MyUnSafeCommon
{
public static ref T GetArrayDataReference<T>(T[] array)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (array.Length == 0) throw new ArgumentException("Array cannot be empty", nameof(array));
return ref array[0];
}
//public unsafe static ref T GetArrayDataReference<T>(T[] array) where T : struct
//{
// fixed (T* ptr = array)
// {
// return ref ArrayElementAsRef<T>(ptr, 0);
// }
//}
//public unsafe static ref T ArrayElementAsRef<T>(void* ptr, int index) where T : struct
//{
// return ref Unity.Collections.LowLevel.Unsafe.UnsafeUtility.ArrayElementAsRef<T>(ptr, index);
//}
}
public static class MyBitOperations
{
// 计算一个整数的位计数(也称为汉明重量)
public static int PopCount(uint value)
{
int count = 0;
while (value != 0)
{
value &= value - 1; // 清除最低位的1
count++;
}
return count;
}
// 如果需要处理long或int等其他类型可以添加重载
public static int PopCount(int value)
{
return PopCount((uint)value); // 对于int简单地将其视为无符号的uint来处理
}
// 对于long你可能需要更复杂的处理或额外的迭代但基本思想相同
public static int PopCount(long value)
{
int count = 0;
value = value - ((value >> 1) & 0x5555555555555555L); // 每两位一组求和
value = (value & 0x3333333333333333L) + ((value >> 2) & 0x3333333333333333L); // 每四位一组求和
value = (value + (value >> 4)) & 0x0f0f0f0f0f0f0f0fL; // 每八位一组求和
count = (int)((value * 0x0101010101010101L) >> 56); // 计算总和
return count;
}
// 向右旋转指定数量的位等效于BitOperations.RotateRight
public static uint RotateRight(uint value, int count)
{
// 确保旋转位数在有效范围内对于uint0到31
count &= 31;
// 使用位移和位或操作来实现旋转
// 先右移count位
uint rightShifted = value >> count;
// 然后左移(32 - count)位,并将结果与右移的结果进行位或操作
// 注意由于uint是无符号的所以左移不会导致符号扩展
uint leftShifted = (value << (32 - count)) & 0xFFFFFFFF; // 实际上对于uint& 0xFFFFFFFF是多余的但在这里为了清晰性而保留
// 组合结果
return rightShifted | leftShifted;
}
// 如果需要处理ulong可以添加类似的重载
public static ulong RotateRight(ulong value, int count)
{
// 确保旋转位数在有效范围内对于ulong0到63
count &= 63;
// 使用位移和位或操作来实现旋转
// 注意ulong需要64位操作
ulong rightShifted = value >> count;
ulong leftShifted = (value << (64 - count)) & 0xFFFFFFFFFFFFFFFF; // 同样对于ulong& 0xFFFFFFFFFFFFFFFF是多余的但保留以增加清晰性
// 组合结果
return rightShifted | leftShifted;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: d82a940594010314bbfb9de2d3865d64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2a9692e254d34f34aa83151367615810
guid: 06ea5793f83f5ab4e83233d686814f83
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1cb7f59fb73e5f74cb7d6d787eadcbd1
guid: a3e5397cebfd55d4d950fc3d7fcafd8f
PluginImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b70ee69b176c61f4eb6db50741d13b0b
guid: 7079d9113f8bfda4db8a78fffdd414a9
PluginImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d58c54bd61da7884f97da0f6bea3e4f5
guid: 677f64bf577a1724699f45841c5bcb04
PluginImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b26dd54a0d93ba0469d1f9307e0e4b43
guid: c92fb77cb5b063541bd04cb8a496013f
PluginImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c85535aab73500e4e93bc6a3430ff694
guid: f82937a8bdf895f4788174f079d4941d
folderAsset: yes
DefaultImporter:
externalObjects: {}

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d2ea7bc8e7d2a9d4383c5e586779c6ad
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 99103839218b11c42820fbe24d848833
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9de3173972725794ab0d4d305c3f88b0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 152604cac101e294ab24dbfab7954669
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 6ea315d0fd7389c41b19996891e99ae3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,267 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 705507994}
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
m_GIWorkflowMode: 1
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 500
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 500
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 2
m_PVRDenoiserTypeDirect: 0
m_PVRDenoiserTypeIndirect: 0
m_PVRDenoiserTypeAO: 0
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 0
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 2
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &705507993
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 705507995}
- component: {fileID: 705507994}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!108 &705507994
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 705507993}
m_Enabled: 1
serializedVersion: 8
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_CookieSize: 10
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_Lightmapping: 1
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
--- !u!4 &705507995
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 705507993}
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!1 &963194225
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 963194228}
- component: {fileID: 963194227}
- component: {fileID: 963194226}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &963194226
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 963194225}
m_Enabled: 1
--- !u!20 &963194227
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 963194225}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_GateFitMode: 2
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &963194228
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 963194225}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a8eb82c88394f8a409faf989c85e64b4
guid: 3efde08a75dea6141840226c711cab4b
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -0,0 +1,80 @@
using OptimeGBA;
using System;
using UnityEngine;
public class AudioProvider : MonoBehaviour
{
[SerializeField]
private AudioSource m_as;
private RingBuffer<float> _buffer;
private TimeSpan lastElapsed;
public double audioFPS { get; private set; }
float lastData = 0;
public void Awake()
{
//var dummy = AudioClip.Create("dummy", 1,2, AudioSettings.outputSampleRate, false);
AudioClip clip = AudioClip.Create("blank", GbaAudio.SampleRate * 2, 2, GbaAudio.SampleRate, true);
_buffer = new RingBuffer<float>(GbaAudio.SampleRate * 2);
m_as.clip = clip;
m_as.playOnAwake = true;
//m_as.loop = true;
m_as.spatialBlend = 1;
}
public void Initialize()
{
if (!m_as.isPlaying)
{
m_as.Play();
}
}
private void OnAudioFilterRead(float[] data, int channels)
{
int step = channels;
step = 1;
for (int i = 0; i < data.Length; i += step)
{
if (_buffer.TryRead(out float rawData))
{
data[i] = rawData;
}
else
{
break;
}
}
//int step = channels;
//step = 1;
//for (int i = 0; i < data.Length; i += step)
//{
// float rawFloat = lastData;
// if (_buffer.TryRead(out float rawData))
// {
// rawFloat = rawData;
// }
// data[i] = rawFloat;
// for (int fill = 1; fill < step; fill++)
// data[i + fill] = rawFloat;
// lastData = rawFloat;
//}
}
public void AudioReady(float[] data)
{
if (!Emulator.instance.EnableAudio) return;
var current = Emulator.sw.Elapsed;
var delta = current - lastElapsed;
lastElapsed = current;
audioFPS = 1d / delta.TotalSeconds;
for (int i = 0; i < data.Length; i++)
{
_buffer.Write(data[i]);
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 300995068a952f743aaddf6f4736c84c
guid: af17dd239b0eea44592e0cd125b2e3b9
MonoImporter:
externalObjects: {}
serializedVersion: 2

168
Assets/emulator/BlipBuf.cs Normal file
View File

@ -0,0 +1,168 @@
// Inspired by Blargg's blip-buf
using System;
namespace OptimeGBA
{
public class BlipBuf
{
const int KERNEL_RESOLUTION = 1024;
float[] Kernel;
int KernelSize = 0;
float[] ChannelValsL;
float[] ChannelValsR;
double[] ChannelSample;
double[] ChannelRealSample;
float[] BufferL;
float[] BufferR;
int BufferPos = 0;
int BufferSize = 0;
public float CurrentValL = 0;
public float CurrentValR = 0;
double CurrentSampleInPos = 0;
double CurrentSampleOutPos = 0;
public BlipBuf(int kernelSize, bool normalize, int channels)
{
ChannelValsL = new float[channels];
ChannelValsR = new float[channels];
ChannelSample = new double[channels];
ChannelRealSample = new double[channels];
BufferSize = 32768;
BufferL = new float[BufferSize];
BufferR = new float[BufferSize];
SetKernelSize(kernelSize, normalize, true);
}
public void SetKernelSize(int kernelSize, bool normalize, bool enabled)
{
Kernel = new float[kernelSize * KERNEL_RESOLUTION];
KernelSize = kernelSize;
if ((kernelSize & (kernelSize - 1)) != 0)
{
throw new ArgumentException("Kernel size not power of 2:" + kernelSize);
}
for (int i = 0; i < KERNEL_RESOLUTION; i++)
{
float sum = 0;
for (int j = 0; j < kernelSize; j++)
{
if (enabled)
{
float x = j - kernelSize / 2F;
x += (KERNEL_RESOLUTION - i - 1) / (float)KERNEL_RESOLUTION;
x *= (float)Math.PI;
float sinc = (float)Math.Sin(x) / x;
float lanzcosWindow = (float)Math.Sin((float)x / kernelSize) / ((float)x / kernelSize);
if (x == 0)
{
Kernel[i * kernelSize + j] = 1;
}
else
{
Kernel[i * kernelSize + j] = sinc * lanzcosWindow;
}
sum += Kernel[i * kernelSize + j];
}
else
{
if (j == kernelSize / 2)
{
Kernel[i * kernelSize + j] = 1;
}
else
{
Kernel[i * kernelSize + j] = 0;
}
}
}
if (normalize && enabled)
{
for (int j = 0; j < kernelSize; j++)
{
Kernel[i * kernelSize + j] /= sum;
}
}
}
}
public void Reset()
{
BufferPos = 0;
CurrentValL = 0;
CurrentValR = 0;
for (int i = 0; i < BufferSize; i++)
{
BufferL[i] = 0;
BufferR[i] = 0;
}
}
public void SetValue(int channel, double sample, float valL, float valR)
{
// Tracking to allow submitting value for different channels out of order
double realSample = sample;
double dist = sample - ChannelRealSample[channel];
sample = ChannelSample[channel] + dist;
if (sample >= CurrentSampleInPos)
{
CurrentSampleInPos = sample;
}
if (sample < CurrentSampleOutPos)
{
//Console.WriteLine("Tried to set amplitude backward in time!");
}
ChannelSample[channel] = sample;
ChannelRealSample[channel] = realSample;
if (valL != ChannelValsL[channel] || valR != ChannelValsR[channel])
{
float diffL = valL - ChannelValsL[channel];
float diffR = valR - ChannelValsR[channel];
int subsamplePos = (int)Math.Floor((sample % 1) * KERNEL_RESOLUTION);
// Add our bandlimited impulse to the difference buffer
int kBufPos = (BufferPos + (int)(Math.Floor(sample) - CurrentSampleOutPos)) % BufferSize;
for (int i = 0; i < KernelSize; i++)
{
float kernelVal = Kernel[KernelSize * subsamplePos + i];
BufferL[kBufPos] += kernelVal * diffL;
BufferR[kBufPos] += kernelVal * diffR;
kBufPos = (kBufPos + 1) % BufferSize;
}
}
ChannelValsL[channel] = valL;
ChannelValsR[channel] = valR;
}
public void ReadOutSample()
{
CurrentValL += BufferL[BufferPos];
CurrentValR += BufferR[BufferPos];
BufferL[BufferPos] = 0;
BufferR[BufferPos] = 0;
BufferPos = (BufferPos + 1) % BufferSize;
CurrentSampleOutPos++;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d321636c09403e249a26ee8f85908765
guid: 89bf553b1fa17dd4fa93a74803b75d0a
MonoImporter:
externalObjects: {}
serializedVersion: 2

254
Assets/emulator/CoreUtil.cs Normal file
View File

@ -0,0 +1,254 @@
using System;
using System.Runtime.CompilerServices;
namespace OptimeGBA
{
public sealed class Bits
{
public const uint BIT_0 = (1 << 0);
public const uint BIT_1 = (1 << 1);
public const uint BIT_2 = (1 << 2);
public const uint BIT_3 = (1 << 3);
public const uint BIT_4 = (1 << 4);
public const uint BIT_5 = (1 << 5);
public const uint BIT_6 = (1 << 6);
public const uint BIT_7 = (1 << 7);
public const uint BIT_8 = (1 << 8);
public const uint BIT_9 = (1 << 9);
public const uint BIT_10 = (1 << 10);
public const uint BIT_11 = (1 << 11);
public const uint BIT_12 = (1 << 12);
public const uint BIT_13 = (1 << 13);
public const uint BIT_14 = (1 << 14);
public const uint BIT_15 = (1 << 15);
public const uint BIT_16 = (1 << 16);
public const uint BIT_17 = (1 << 17);
public const uint BIT_18 = (1 << 18);
public const uint BIT_19 = (1 << 19);
public const uint BIT_20 = (1 << 20);
public const uint BIT_21 = (1 << 21);
public const uint BIT_22 = (1 << 22);
public const uint BIT_23 = (1 << 23);
public const uint BIT_24 = (1 << 24);
public const uint BIT_25 = (1 << 25);
public const uint BIT_26 = (1 << 26);
public const uint BIT_27 = (1 << 27);
public const uint BIT_28 = (1 << 28);
public const uint BIT_29 = (1 << 29);
public const uint BIT_30 = (1 << 30);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool BitTest(uint i, byte bit)
{
return (i & (1 << bit)) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool BitTest(ulong i, byte bit)
{
return (i & (1u << bit)) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool BitTest(long i, byte bit)
{
return (i & (1u << bit)) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint BitSet(uint i, byte bit)
{
return (uint)(i | (uint)(1 << bit));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte BitSet(byte i, byte bit)
{
return (byte)(i | (byte)(1 << bit));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte BitClear(byte i, byte bit)
{
return (byte)(i & ~(byte)(1 << bit));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint BitRange(uint i, byte start, byte end)
{
return (i >> start) & (0xFFFFFFFF >> (31 - (end - start)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte BitReverse8(byte i)
{
return (byte)((i * 0x0202020202U & 0x010884422010U) % 1023);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint LogicalShiftLeft32(uint n, byte bits)
{
return n << bits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint LogicalShiftRight32(uint n, byte bits)
{
return n >> bits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ArithmeticShiftRight32(uint n, byte bits)
{
return (uint)((int)n >> bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint RotateRight32(uint n, byte bits)
{
return (n >> bits) | (n << -bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong RotateRight64(ulong n, byte bits)
{
return (n >> bits) | (n << -bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteIn(long n, int pos)
{
return (byte)(n >> (pos * 8));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteIn(uint n, int pos)
{
return (byte)(n >> (pos * 8));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteIn(ulong n, int pos)
{
return (byte)(n >> (pos * 8));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteIn(long n, uint pos)
{
return (byte)(n >> (int)(pos * 8));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteIn(ulong n, uint pos)
{
return (byte)(n >> (int)(pos * 8));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint SetByteIn(uint n, byte val, uint pos)
{
uint mask = ~(0xFFU << (int)(pos * 8));
uint or = (uint)(val << (int)(pos * 8));
return (n & mask) | or;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong SetByteIn(ulong n, byte val, uint pos)
{
ulong mask = ~(0xFFUL << (int)(pos * 8));
ulong or = (ulong)((ulong)val << (int)(pos * 8));
return (n & mask) | or;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long SetByteIn(long n, byte val, uint pos)
{
long mask = ~(0xFFL << (int)(pos * 8));
long or = (long)((long)val << (int)(pos * 8));
return (n & mask) | or;
}
// public bool bitReset(uint i, uint bit)
// {
// return i & (~(1 << bit));
// }
// public bool bitSetValue(uint i, uint bit, bool value)
// {
// if (value)
// {
// return i | (1 << bit);
// }
// else
// {
// return i & (~(1 << bit));
// }
// }
}
public class CoreUtil
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Swap<T>(ref T one, ref T two)
{
T temp = one;
one = two;
two = temp;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static sbyte SignExtend8(byte val, int pos)
{
return (sbyte)(((sbyte)val << (7 - pos)) >> (7 - pos));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short SignExtend16(ushort val, int pos)
{
return (short)(((short)val << (15 - pos)) >> (15 - pos));
}
public static byte[] FloatArrayToByteBuffer(float[] datas)
{
byte[] result = new byte[datas.Length * 4];
for (int i = 0; i < datas.Length; i++)
{
byte[] signalBytes = BitConverter.GetBytes(datas[i]);
result[4 * i] = signalBytes[0];
result[4 * i + 1] = signalBytes[1];
result[4 * i + 2] = signalBytes[2];
result[4 * i + 3] = signalBytes[3];
}
return result;
}
public static float[] ByteToFloatArray(byte[] srcByte)
{
unsafe
{
int FLOATLEN = sizeof(float);
int srcLen = srcByte.Length;
int dstLen = srcLen / FLOATLEN;
float[] dstFloat = new float[dstLen];
for (int i = 0; i < dstLen; i++)
{
float temp = 0.0F;
void* pf = &temp;
fixed (byte* pxb = srcByte)
{
byte* px = pxb;
px += i * FLOATLEN;
for (int j = 0; j < FLOATLEN; j++)
{
*((byte*)pf + j) = *(px + j);
}
dstFloat[i] = temp;
}
}
return dstFloat;
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8ad832993e3b0bc4695925ea366d4d9e
guid: c452c8124018a2748ba32716b989a1ea
MonoImporter:
externalObjects: {}
serializedVersion: 2

86
Assets/emulator/Cp15.cs Normal file
View File

@ -0,0 +1,86 @@
using System;
using static Util;
namespace OptimeGBA
{
public class Cp15
{
/*Nds Nds;
public Cp15(Nds nds)
{
Nds = nds;
}*/
public uint ControlRegister;
public uint DataTcmSettings;
public uint InstTcmSettings;
public void TransferTo(uint opcode1, uint rdVal, uint cRn, uint cRm, uint opcode2)
{
uint reg = ((cRn & 0xF) << 8) | ((cRm & 0xF) << 4) | (opcode2 & 0x7);
switch (reg)
{
case 0x100:
ControlRegister = rdVal;
ControlRegister |= 0b00000000000000000000000001111000;
ControlRegister &= 0b00000000000011111111000010000101;
//Nds.Mem9.UpdateTcmSettings();
break;
case 0x704:
case 0x782:
//Nds.Cpu9.Halted = true;
break;
case 0x910:
DataTcmSettings = rdVal;
//Nds.Mem9.UpdateTcmSettings();
break;
case 0x911:
InstTcmSettings = rdVal;
//Nds.Mem9.UpdateTcmSettings();
break;
default:
// Debug.Log($"UNIMPLEMENTED TO CP15 {opcode1},C{cRn},C{cRm},{opcode2}: {HexN(rdVal, 8)}");
break;
}
}
public uint TransferFrom(uint opcode1, uint cRn, uint cRm, uint opcode2)
{
uint val = 0;
uint reg = ((cRn & 0xF) << 8) | ((cRm & 0xF) << 4) | (opcode2 & 0x7);
switch (reg)
{
case 0x000: // ID register
val = 0x41059461;
break;
case 0x001:
val = 0x0F0D2112;
break;
case 0x100:
val = ControlRegister;
break;
case 0x910:
val = DataTcmSettings;
break;
case 0x911:
val = InstTcmSettings;
break;
default:
//Debug.Log($"UNIMPLEMENTED FROM CP15 {opcode1},C{cRn},C{cRm},{opcode2}");
break;
}
return val;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4c4deba1667702f49b08a4ccab53688e
guid: 082cf1137d5d5114c8755e473d5857b5
MonoImporter:
externalObjects: {}
serializedVersion: 2

29
Assets/emulator/Dma.cs Normal file
View File

@ -0,0 +1,29 @@
using static OptimeGBA.Bits;
using System;
namespace OptimeGBA
{
public enum DmaStartTiming
{
Immediately = 0,
VBlank = 1,
HBlank = 2,
Special = 3,
}
public enum DmaDestAddrCtrl
{
Increment = 0,
Decrement = 1,
Fixed = 2,
IncrementReload = 3,
}
public enum DmaSrcAddrCtrl
{
Increment = 0,
Decrement = 1,
Fixed = 2,
PROHIBITED = 3,
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1b25dbe9e7a7eb4a8af44963c72a602
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

444
Assets/emulator/DmaGba.cs Normal file
View File

@ -0,0 +1,444 @@
using static OptimeGBA.Bits;
using System;
namespace OptimeGBA
{
public enum DmaStartTimingGba
{
Immediately = 0,
VBlank = 1,
HBlank = 2,
Special = 3,
}
public sealed class DmaChannelGba
{
public uint DMASAD;
public uint DMADAD;
public uint DMACNT_L;
public uint DmaSource;
public uint DmaDest;
public uint DmaLength;
// DMACNT_H
public DmaDestAddrCtrl DestAddrCtrl;
public DmaSrcAddrCtrl SrcAddrCtrl;
public bool Repeat;
public bool TransferType;
public bool GamePakDRQ;
public DmaStartTimingGba StartTiming;
public bool FinishedIRQ;
public bool Enabled; // Don't directly set to false, use Disable()
public uint DMACNT_H;
public byte ReadHwio8(uint addr)
{
// DMASAD, DMADAD, and DMACNT_L are write-only
byte val = 0;
switch (addr)
{
case 0x0A: // DMACNT_H B0
case 0x0B: // DMACNT_H B1
val = GetByteIn(GetControl(), addr & 1);
break;
}
return val;
}
public void WriteHwio8(uint addr, byte val)
{
switch (addr)
{
case 0x00: // DMASAD B0
case 0x01: // DMASAD B1
case 0x02: // DMASAD B2
case 0x03: // DMASAD B3
DMASAD = SetByteIn(DMASAD, val, addr & 3);
break;
case 0x04: // DMADAD B0
case 0x05: // DMADAD B1
case 0x06: // DMADAD B2
case 0x07: // DMADAD B3
DMADAD = SetByteIn(DMADAD, val, addr & 3);
break;
case 0x08: // DMACNT_L B0
case 0x09: // DMACNT_L B1
DMACNT_L = SetByteIn(DMACNT_L, val, addr & 1);
break;
case 0x0A: // DMACNT_H B0
case 0x0B: // DMACNT_H B1
DMACNT_H = SetByteIn(DMACNT_H, val, addr & 1);
UpdateControl();
break;
}
}
public void UpdateControl()
{
DestAddrCtrl = (DmaDestAddrCtrl)BitRange(DMACNT_H, 5, 6);
SrcAddrCtrl = (DmaSrcAddrCtrl)BitRange(DMACNT_H, 7, 8);
Repeat = BitTest(DMACNT_H, 9);
TransferType = BitTest(DMACNT_H, 10);
GamePakDRQ = BitTest(DMACNT_H, 11);
StartTiming = (DmaStartTimingGba)BitRange(DMACNT_H, 12, 13);
FinishedIRQ = BitTest(DMACNT_H, 14);
if (BitTest(DMACNT_H, 15))
{
Enable();
}
else
{
Disable();
}
}
public uint GetControl()
{
uint val = 0;
val |= ((uint)DestAddrCtrl & 0b11) << 5;
val |= ((uint)SrcAddrCtrl & 0b11) << 7;
if (Repeat) val = BitSet(val, 9);
if (TransferType) val = BitSet(val, 10);
if (GamePakDRQ) val = BitSet(val, 11);
val |= ((uint)StartTiming & 0b11) << 12;
if (FinishedIRQ) val = BitSet(val, 14);
if (Enabled) val = BitSet(val, 15);
DMACNT_H = val;
return val;
}
public void Enable()
{
if (!Enabled)
{
DmaSource = DMASAD;
DmaDest = DMADAD;
DmaLength = DMACNT_L;
}
Enabled = true;
GetControl();
}
public void Disable()
{
Enabled = false;
GetControl();
}
}
public unsafe sealed class DmaGba
{
Gba Gba;
public DmaChannelGba[] Ch = new DmaChannelGba[4] {
new DmaChannelGba(),
new DmaChannelGba(),
new DmaChannelGba(),
new DmaChannelGba(),
};
static readonly uint[] DmaSourceMask = { 0x07FFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF };
static readonly uint[] DmaDestMask = { 0x07FFFFFF, 0x07FFFFFF, 0x07FFFFFF, 0x0FFFFFFFF };
public bool DmaLock;
public DmaGba(Gba gba)
{
Gba = gba;
}
public byte ReadHwio8(uint addr)
{
if (addr >= 0x40000B0 && addr <= 0x40000BB)
{
return Ch[0].ReadHwio8(addr - 0x40000B0);
}
else if (addr >= 0x40000BC && addr <= 0x40000C7)
{
return Ch[1].ReadHwio8(addr - 0x40000BC);
}
else if (addr >= 0x40000C8 && addr <= 0x40000D3)
{
return Ch[2].ReadHwio8(addr - 0x40000C8);
}
else if (addr >= 0x40000D4 && addr <= 0x40000DF)
{
return Ch[3].ReadHwio8(addr - 0x40000D4);
}
throw new Exception("This shouldn't happen.");
}
public void WriteHwio8(uint addr, byte val)
{
if (addr >= 0x40000B0 && addr <= 0x40000BB)
{
bool oldEnabled = Ch[0].Enabled;
Ch[0].WriteHwio8(addr - 0x40000B0, val);
if (!oldEnabled && Ch[0].Enabled) ExecuteImmediate(0);
return;
}
else if (addr >= 0x40000BC && addr <= 0x40000C7)
{
bool oldEnabled = Ch[1].Enabled;
Ch[1].WriteHwio8(addr - 0x40000BC, val);
if (!oldEnabled && Ch[1].Enabled) ExecuteImmediate(1);
return;
}
else if (addr >= 0x40000C8 && addr <= 0x40000D3)
{
bool oldEnabled = Ch[2].Enabled;
Ch[2].WriteHwio8(addr - 0x40000C8, val);
if (!oldEnabled && Ch[2].Enabled) ExecuteImmediate(2);
return;
}
else if (addr >= 0x40000D4 && addr <= 0x40000DF)
{
bool oldEnabled = Ch[3].Enabled;
Ch[3].WriteHwio8(addr - 0x40000D4, val);
if (!oldEnabled && Ch[3].Enabled) ExecuteImmediate(3);
return;
}
throw new Exception("This shouldn't happen.");
}
public void ExecuteDma(DmaChannelGba c, uint ci)
{
DmaLock = true;
// Least significant 28 (or 27????) bits
c.DmaSource &= DmaSourceMask[ci];
c.DmaDest &= DmaDestMask[ci];
if (ci == 3)
{
// DMA 3 is 16-bit length
c.DmaLength &= 0b1111111111111111;
// Value of zero is treated as maximum length
if (c.DmaLength == 0) c.DmaLength = 0x10000;
}
else
{
// DMA 0-2 are 14-bit length
c.DmaLength &= 0b11111111111111;
// Value of zero is treated as maximum length
if (c.DmaLength == 0) c.DmaLength = 0x4000;
}
// Debug.Log($"Starting DMA {ci}");
// Debug.Log($"SRC: {Util.HexN(srcAddr, 7)}");
// Debug.Log($"DEST: {Util.HexN(destAddr, 7)}");
// Debug.Log($"LENGTH: {Util.HexN(c.DmaLength, 4)}");
int destOffsPerUnit;
int sourceOffsPerUnit;
if (c.TransferType)
{
switch (c.DestAddrCtrl)
{
case DmaDestAddrCtrl.Increment: destOffsPerUnit = +4; break;
case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -4; break;
case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +4; break;
default: destOffsPerUnit = 0; break;
}
switch (c.SrcAddrCtrl)
{
case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +4; break;
case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -4; break;
default: sourceOffsPerUnit = 0; break;
}
}
else
{
switch (c.DestAddrCtrl)
{
case DmaDestAddrCtrl.Increment: destOffsPerUnit = +2; break;
case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -2; break;
case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +2; break;
default: destOffsPerUnit = 0; break;
}
switch (c.SrcAddrCtrl)
{
case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +2; break;
case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -2; break;
default: sourceOffsPerUnit = 0; break;
}
}
uint origLength = c.DmaLength;
if (c.TransferType)
{
for (; c.DmaLength > 0; c.DmaLength--)
{
Gba.Mem.Write32(c.DmaDest & ~3u, Gba.Mem.Read32(c.DmaSource & ~3u));
Gba.Tick(Gba.Cpu.Timing32[(c.DmaSource >> 24) & 0xF]);
Gba.Tick(Gba.Cpu.Timing32[(c.DmaDest >> 24) & 0xF]);
c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest);
c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource);
}
}
else
{
for (; c.DmaLength > 0; c.DmaLength--)
{
Gba.Mem.Write16(c.DmaDest & ~1u, Gba.Mem.Read16(c.DmaSource & ~1u));
Gba.Tick(Gba.Cpu.Timing8And16[(c.DmaSource >> 24) & 0xF]);
Gba.Tick(Gba.Cpu.Timing8And16[(c.DmaDest >> 24) & 0xF]);
c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest);
c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource);
}
}
if (c.DestAddrCtrl == DmaDestAddrCtrl.IncrementReload)
{
c.DmaLength = origLength;
if (c.Repeat)
{
c.DmaDest = c.DMADAD;
}
}
if (c.FinishedIRQ)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.Dma0 + ci);
}
DmaLock = false;
}
public void ExecuteSoundDma(DmaChannelGba c, uint ci)
{
DmaLock = true;
// Least significant 28 (or 27????) bits
uint srcAddr = c.DmaSource & 0b1111111111111111111111111111;
uint destAddr = c.DmaDest & 0b111111111111111111111111111;
// 4 units of 32bits (16 bytes) are transferred to FIFO_A or FIFO_B
for (uint i = 0; i < 4; i++)
{
uint val = Gba.Mem.Read32(srcAddr + 0);
if (destAddr == 0x40000A0)
{
Gba.GbaAudio.A.Insert((byte)val);
Gba.GbaAudio.A.Insert((byte)(val >>= 8));
Gba.GbaAudio.A.Insert((byte)(val >>= 8));
Gba.GbaAudio.A.Insert((byte)(val >>= 8));
}
else if (destAddr == 0x40000A4)
{
Gba.GbaAudio.B.Insert((byte)val);
Gba.GbaAudio.B.Insert((byte)(val >>= 8));
Gba.GbaAudio.B.Insert((byte)(val >>= 8));
Gba.GbaAudio.B.Insert((byte)(val >>= 8));
}
else
{
Gba.Mem.Write8(destAddr + 0, (byte)val);
Gba.Mem.Write8(destAddr + 1, (byte)(val >>= 8));
Gba.Mem.Write8(destAddr + 2, (byte)(val >>= 8));
Gba.Mem.Write8(destAddr + 3, (byte)(val >>= 8));
}
switch (c.SrcAddrCtrl)
{
case DmaSrcAddrCtrl.Increment: srcAddr += 4; break;
case DmaSrcAddrCtrl.Decrement: srcAddr -= 4; break;
case DmaSrcAddrCtrl.Fixed: break;
}
// Applying proper timing to sound DMAs causes crackling in certain games including PMD.
// This only happens with scheduled timers, which leads me to believe the real problem is in there.
// PROBLEM SOLVED.... my timers were 1 cycle too slow to reload
Gba.Cpu.InstructionCycles += (Gba.Cpu.Timing32[(c.DmaSource >> 24) & 0xF]);
Gba.Cpu.InstructionCycles += (Gba.Cpu.Timing32[(c.DmaDest >> 24) & 0xF]);
}
c.DmaSource = srcAddr;
if (c.FinishedIRQ)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.Dma0 + ci);
}
DmaLock = false;
}
public void ExecuteImmediate(uint ci)
{
DmaChannelGba c = Ch[ci];
if (c.Enabled && c.StartTiming == DmaStartTimingGba.Immediately)
{
c.Disable();
ExecuteDma(c, ci);
}
}
public void RepeatFifoA()
{
if (!DmaLock)
{
if (Ch[1].StartTiming == DmaStartTimingGba.Special)
{
ExecuteSoundDma(Ch[1], 1);
}
}
}
public void RepeatFifoB()
{
if (!DmaLock)
{
if (Ch[2].StartTiming == DmaStartTimingGba.Special)
{
ExecuteSoundDma(Ch[2], 2);
}
}
}
public void RepeatHblank()
{
if (!DmaLock)
{
for (uint ci = 0; ci < 4; ci++)
{
DmaChannelGba c = Ch[ci];
if (c.StartTiming == DmaStartTimingGba.HBlank)
{
c.DmaLength = c.DMACNT_L;
ExecuteDma(c, ci);
}
}
}
}
public void RepeatVblank()
{
if (!DmaLock)
{
for (uint ci = 0; ci < 4; ci++)
{
DmaChannelGba c = Ch[ci];
if (c.StartTiming == DmaStartTimingGba.VBlank)
{
c.DmaLength = c.DMACNT_L;
ExecuteDma(c, ci);
}
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36717f7dc90280e41826611c592f97a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

246
Assets/emulator/Emulator.cs Normal file
View File

@ -0,0 +1,246 @@
using OptimeGBA;
using System;
using System.IO;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
public class Emulator : MonoBehaviour
{
public static Emulator instance;
const int FrameCycles = 70224 * 4;
const int ScanlineCycles = 1232;
const float FrameRate = 59.7275f;
static bool SyncToAudio = true;
//public Renderer screenRenderer;
public VideoProvider videoProvider;
public AudioProvider audioProvider;
public static System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
public bool ShowBackBuf = false;
public bool RunEmulator;
public bool EnableAudio;
public bool BootBIOS = false;
public bool RomLoaded { get; private set; } = false;
public Gba gba;
Thread EmulationThread;
AutoResetEvent ThreadSync = new AutoResetEvent(false);
private int _samplesAvailable;
//private PipeStream _pipeStream;
private byte[] _buffer;
public float audioGain = 1.0f;
// key delegate
public delegate bool IsKeyPressed(GBAKeyCode keyCode);
public IsKeyPressed KeyPressed;
public Button btnStart;
private void Awake()
{
instance = this;
//BetterStreamingAssets.Initialize();
// must set it to 60 or it won't sync with audio or run too fast.
Application.targetFrameRate = (int)FrameRate;
//audioSource = GetComponent<AudioSource>();
//AudioClip clip = AudioClip.Create("blank", GbaAudio.SampleRate * 2, 2, GbaAudio.SampleRate, true);
//audioSource.clip = clip;
//audioSource.playOnAwake = true;
//audioSource.enabled = false;
//screenRenderer.material.mainTexture = new Texture2D(240, 160, TextureFormat.RGBA32, false, false);
// Get Unity Buffer size
AudioSettings.GetDSPBufferSize(out int bufferLength, out _);
_samplesAvailable = bufferLength;
// Must be set to 32768
var audioConfig = AudioSettings.GetConfiguration();
audioConfig.sampleRate = GbaAudio.SampleRate;
AudioSettings.Reset(audioConfig);
// Prepare our buffer
//_pipeStream = new PipeStream();
//_pipeStream.MaxBufferLength = _samplesAvailable * 2 * sizeof(float);
_buffer = new byte[_samplesAvailable * 2 * sizeof(float)];
}
void Start()
{
byte[] bios = Resources.Load<TextAsset>("gba_bios.bin").bytes;
//byte[] bios = BetterStreamingAssets.ReadAllBytes("gba_bios.bin");
Debug.Log(bios.Length);
gba = new Gba(new ProviderGba(bios, new byte[0], "", audioProvider.AudioReady) { BootBios = BootBIOS });
EmulationThread = new Thread(EmulationThreadHandler);
EmulationThread.Name = "Emulation Core";
EmulationThread.Start();
btnStart.onClick.AddListener(
() =>
{
byte[] romdata = Resources.Load<TextAsset>("mario_world.gba").bytes;
LoadRom(romdata, "mario_world.gba");
}
);
}
// Update is called once per frame
void Update()
{
if (RomLoaded)
{
videoProvider.OnRenderFrame();
}
OnUpdateFrame();
}
public void LoadRom(byte[] rom, string name)
{
string savPath = Application.persistentDataPath + "/" + name.Substring(0, name.Length - 3) + "sav";
byte[] sav = new byte[0];
if (File.Exists(savPath))
{
Debug.Log($"{savPath} exists, loading");
try
{
sav = File.ReadAllBytes(savPath);
}
catch
{
Debug.Log("Failed to load .sav file!");
}
}
else
{
Debug.Log(".sav not available");
}
LoadRomAndSave(rom, sav, savPath);
Debug.Log("Load Rom Success");
audioProvider.Initialize();
RomLoaded = true;
RunEmulator = true;
}
public void LoadRomAndSave(byte[] rom, byte[] sav, string savPath)
{
var bios = gba.Provider.Bios;
gba = new Gba(new ProviderGba(bios, rom, savPath, audioProvider.AudioReady) { BootBios = BootBIOS });
gba.Mem.SaveProvider.LoadSave(sav);
}
public void ResetGba()
{
byte[] save = gba.Mem.SaveProvider.GetSave();
ProviderGba p = gba.Provider;
gba = new Gba(p);
gba.Mem.SaveProvider.LoadSave(save);
}
public void EmulationThreadHandler()
{
while (true)
{
ThreadSync.WaitOne();
int cyclesLeft = 70224 * 4;
while (cyclesLeft > 0 && !gba.Cpu.Errored)
{
cyclesLeft -= (int)gba.Step();
}
while (!SyncToAudio && !gba.Cpu.Errored && RunEmulator)
{
gba.Step();
}
}
}
public int GetOutputSampleRate()
{
return AudioSettings.outputSampleRate;
}
public int GetSamplesAvailable()
{
return _samplesAvailable;
}
//private void OnAudioFilterRead(float[] data, int channels)
//{
// if (!EnableAudio) return;
// int r = _pipeStream.Read(_buffer, 0, data.Length * sizeof(float));
// float[] pcm = CoreUtil.ByteToFloatArray(_buffer);
// Array.Copy(pcm, data, data.Length);
//}
public void RunCycles(int cycles)
{
while (cycles > 0 && !gba.Cpu.Errored && RunEmulator)
{
cycles -= (int)gba.Step();
}
}
int CyclesLeft;
public void RunFrame()
{
CyclesLeft += FrameCycles;
while (CyclesLeft > 0 && !gba.Cpu.Errored)
{
CyclesLeft -= (int)gba.Step();
}
}
public void RunScanline()
{
CyclesLeft += ScanlineCycles;
while (CyclesLeft > 0 && !gba.Cpu.Errored)
{
CyclesLeft -= (int)gba.Step();
}
}
public void OnUpdateFrame()
{
gba.Keypad.B = KeyPressed(GBAKeyCode.B);
gba.Keypad.A = KeyPressed(GBAKeyCode.A);
gba.Keypad.Left = KeyPressed(GBAKeyCode.Left);
gba.Keypad.Up = KeyPressed(GBAKeyCode.Up);
gba.Keypad.Right = KeyPressed(GBAKeyCode.Right);
gba.Keypad.Down = KeyPressed(GBAKeyCode.Down);
gba.Keypad.Start = KeyPressed(GBAKeyCode.Start);
gba.Keypad.Select = KeyPressed(GBAKeyCode.Select);
gba.Keypad.L = KeyPressed(GBAKeyCode.L);
gba.Keypad.R = KeyPressed(GBAKeyCode.R);
SyncToAudio = !(Input.GetKey(KeyCode.Tab) || Input.GetKey(KeyCode.Space));
if (RunEmulator)
{
ThreadSync.Set();
}
if (gba.Mem.SaveProvider.Dirty)
{
DumpSav();
}
}
public void DumpSav()
{
try
{
//File.WriteAllBytesAsync(gba.Provider.SavPath, gba.Mem.SaveProvider.GetSave());
File.WriteAllBytes(gba.Provider.SavPath, gba.Mem.SaveProvider.GetSave());
}
catch
{
Debug.Log("Failed to write .sav file!");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 515e71167c74b044984b170a2a141f10
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,38 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EmulatorGUI : MonoBehaviour
{
private Dictionary<GBAKeyCode, KeyCode> keyboardKeyCodeMap;
private Emulator emulator;
void Start()
{
keyboardKeyCodeMap = new Dictionary<GBAKeyCode, KeyCode>()
{
{ GBAKeyCode.Start,KeyCode.Return},
{ GBAKeyCode.Select,KeyCode.Backspace},
{ GBAKeyCode.Left,KeyCode.A},
{ GBAKeyCode.Right,KeyCode.D},
{ GBAKeyCode.Up,KeyCode.W},
{ GBAKeyCode.Down,KeyCode.S},
{ GBAKeyCode.A,KeyCode.J},
{ GBAKeyCode.B,KeyCode.K},
{ GBAKeyCode.L,KeyCode.U},
{ GBAKeyCode.R,KeyCode.I},
};
emulator = GameObject.FindObjectOfType<Emulator>();
emulator.KeyPressed += GetKey;
}
public bool GetKey(GBAKeyCode keyCode)
{
#if UNITY_EDITOR || UNITY_STANDALONE
bool input = Input.GetKey( keyboardKeyCodeMap[keyCode]);
if(input) return true;else return false;
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a6d4732564041e4d85a5fbef37546d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,20 @@
{
"name": "GBA",
"rootNamespace": "",
"references": [
"GUID:d3a8dd703fcbca94f97e175973c255f5",
"GUID:43b6de571eb529e4b8cd209457a5f570",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:2665a8d13d1b3f18800f46e256720795",
"GUID:5e90fee04fdf7164fa71eeef34c6a431"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 54cc43fb16a4d83469407e3e8e248065
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

1186
Assets/emulator/GbAudio.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ab02a6abe06f10f468911df855dd7604
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

213
Assets/emulator/Gba.cs Normal file
View File

@ -0,0 +1,213 @@
using System;
namespace OptimeGBA
{
public unsafe sealed class Gba
{
public ProviderGba Provider;
public Scheduler Scheduler;
public AudioCallback AudioCallback;
public MemoryGba Mem;
public Arm7 Cpu;
public GbaAudio GbaAudio;
public Keypad Keypad;
public PpuGba Ppu;
public HwControlGba HwControl;
public DmaGba Dma;
public Timers Timers;
public Gba(ProviderGba provider)
{
Provider = provider;
Scheduler = new Scheduler();
Mem = new MemoryGba(this, provider);
GbaAudio = new GbaAudio(this, Scheduler);
Ppu = new PpuGba(this, Scheduler);
Keypad = new Keypad();
Dma = new DmaGba(this);
HwControl = new HwControlGba(this);
Timers = new Timers(GbaAudio, HwControl, Scheduler, false, true);
Cpu = new Arm7(StateChange, Mem, false, false, null);
Cpu.SetTimingsTable(
Cpu.Timing8And16,
1, // BIOS
1, // Unused
3, // EWRAM
1, // IWRAM
1, // I/O Registers
1, // PPU Palettes
1, // PPU VRAM
1, // PPU OAM
5, // Game Pak ROM/FlashROM
5, // Game Pak ROM/FlashROM
5, // Game Pak ROM/FlashROM
5, // Game Pak ROM/FlashROM
5, // Game Pak ROM/FlashROM
5, // Game Pak ROM/FlashROM
5, // Game Pak SRAM/Flash
5 // Game Pak SRAM/Flash
);
Cpu.SetTimingsTable(
Cpu.Timing32,
1, // BIOS
1, // Unused
6, // EWRAM
1, // IWRAM
1, // I/O Registers
2, // PPU Palettes
2, // PPU VRAM
1, // PPU OAM
8, // Game Pak ROM/FlashROM
8, // Game Pak ROM/FlashROM
8, // Game Pak ROM/FlashROM
8, // Game Pak ROM/FlashROM
8, // Game Pak ROM/FlashROM
8, // Game Pak ROM/FlashROM
8, // Game Pak SRAM/Flash
8 // Game Pak SRAM/Flash
);
Cpu.SetTimingsTable(
Cpu.Timing8And16InstrFetch,
1, // BIOS
1, // Unused
3, // EWRAM
1, // IWRAM
1, // I/O Registers
1, // PPU Palettes
1, // PPU VRAM
1, // PPU OAM
// Compensate for no prefetch buffer 5 -> 2
2, // Game Pak ROM/FlashROM
2, // Game Pak ROM/FlashROM
2, // Game Pak ROM/FlashROM
2, // Game Pak ROM/FlashROM
2, // Game Pak ROM/FlashROM
2, // Game Pak ROM/FlashROM
5, // Game Pak SRAM/Flash
5 // Game Pak SRAM/Flash
);
Cpu.SetTimingsTable(
Cpu.Timing32InstrFetch,
1, // BIOS
1, // Unused
6, // EWRAM
1, // IWRAM
1, // I/O Registers
2, // PPU Palettes
2, // PPU VRAM
1, // PPU OAM
// Compensate for no prefetch buffer 8 -> 4
4, // Game Pak ROM/FlashROM
4, // Game Pak ROM/FlashROM
4, // Game Pak ROM/FlashROM
4, // Game Pak ROM/FlashROM
4, // Game Pak ROM/FlashROM
4, // Game Pak ROM/FlashROM
8, // Game Pak SRAM/Flash
8 // Game Pak SRAM/Flash
);
if (!provider.BootBios)
{
Cpu.SetModeReg(13, Arm7Mode.SVC, 0x03007FE0);
Cpu.SetModeReg(13, Arm7Mode.IRQ, 0x03007FA0);
Cpu.SetModeReg(13, Arm7Mode.USR, 0x03007F00);
// Default Stack Pointer
Cpu.R[13] = Cpu.GetModeReg(13, Arm7Mode.USR);
Cpu.R[15] = 0x08000000;
}
AudioCallback = provider.AudioCallback;
Mem.InitPageTables();
Cpu.InitFlushPipeline();
}
public uint Step()
{
Cpu.CheckInterrupts();
long beforeTicks = Scheduler.CurrentTicks;
if (!Cpu.ThumbState)
{
Scheduler.CurrentTicks += Cpu.ExecuteArm();
}
else
{
Scheduler.CurrentTicks += Cpu.ExecuteThumb();
}
while (Scheduler.CurrentTicks >= Scheduler.NextEventTicks)
{
long current = Scheduler.CurrentTicks;
long next = Scheduler.NextEventTicks;
Scheduler.PopFirstEvent().Callback(current - next);
}
return (uint)(Scheduler.CurrentTicks - beforeTicks);
}
public void DoNothing(long cyclesLate) { }
public void StateChange()
{
Scheduler.AddEventRelative(SchedulerId.None, 0, DoNothing);
}
public uint StateStep()
{
Cpu.CheckInterrupts();
long beforeTicks = Scheduler.CurrentTicks;
if (!Cpu.ThumbState)
{
while (Scheduler.CurrentTicks < Scheduler.NextEventTicks)
{
Scheduler.CurrentTicks += Cpu.ExecuteArm();
}
}
else
{
while (Scheduler.CurrentTicks < Scheduler.NextEventTicks)
{
Scheduler.CurrentTicks += Cpu.ExecuteThumb();
}
}
while (Scheduler.CurrentTicks >= Scheduler.NextEventTicks)
{
long current = Scheduler.CurrentTicks;
long next = Scheduler.NextEventTicks;
Scheduler.PopFirstEvent().Callback(current - next);
}
// Return cycles executed
return (uint)(Scheduler.CurrentTicks - beforeTicks);
}
public void Tick(uint cycles)
{
Scheduler.CurrentTicks += cycles;
}
public void HaltSkip(long cyclesLate)
{
long before = Scheduler.CurrentTicks;
while (!HwControl.Available)
{
long ticksPassed = Scheduler.NextEventTicks - Scheduler.CurrentTicks;
Scheduler.CurrentTicks = Scheduler.NextEventTicks;
Scheduler.PopFirstEvent().Callback(0);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 90fdc7ebe54f77c4da8c510515da9f9d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

483
Assets/emulator/GbaAudio.cs Normal file
View File

@ -0,0 +1,483 @@
using System;
using static OptimeGBA.Bits;
using System.Runtime.CompilerServices;
namespace OptimeGBA
{
public enum ResamplingMode
{
None,
Linear,
Sinc,
SincLowPass,
}
public sealed class CircularBufferOverwriting<T>
{
public uint Size;
public T[] Buffer;
public uint ReadPos = 0;
public uint WritePos = 0;
public CircularBufferOverwriting(uint size)
{
Size = size;
Buffer = new T[Size];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Insert(T data)
{
Buffer[WritePos++] = data;
if (WritePos >= Size)
{
WritePos = 0;
}
return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Pop()
{
T data = Buffer[ReadPos++];
if (ReadPos >= Size)
{
ReadPos = 0;
}
return data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Peek(int offset)
{
return Buffer[(ReadPos + offset) % Size];
}
public void Reset()
{
ReadPos = 0;
WritePos = 0;
}
}
public sealed class CircularBuffer<T>
{
public uint Size;
public T[] Buffer;
public T EmptyValue;
public uint ReadPos = 0;
public uint WritePos = 0;
public uint Entries = 0;
public uint TotalPops = 0;
public uint EmptyPops = 0;
public uint FullInserts = 0;
public uint Collisions = 0;
public CircularBuffer(uint size, T emptyValue)
{
Size = size;
Buffer = new T[Size];
EmptyValue = emptyValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Insert(T data)
{
if (Entries < Size)
{
if (ReadPos == WritePos) Collisions++;
Entries++;
Buffer[WritePos++] = data;
if (WritePos >= Size)
{
WritePos = 0;
}
return true;
}
FullInserts++;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Pop()
{
T data;
TotalPops++;
if (Entries > 0)
{
Entries--;
data = Buffer[ReadPos++];
if (ReadPos >= Size)
{
ReadPos = 0;
}
}
else
{
EmptyPops++;
data = EmptyValue;
}
return data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Peek(int offset)
{
return Buffer[(ReadPos + offset) % Size];
}
public void Reset()
{
Entries = 0;
ReadPos = 0;
WritePos = 0;
}
}
public sealed class GbaAudio
{
Gba Gba;
Scheduler Scheduler;
public GbaAudio(Gba gba, Scheduler scheduler)
{
Gba = gba;
Scheduler = scheduler;
Scheduler.AddEventRelative(SchedulerId.ApuSample, CyclesPerSample, Sample);
}
public GbAudio GbAudio = new GbAudio();
public bool DebugEnableA = true;
public bool DebugEnableB = true;
public CircularBuffer<byte> A = new CircularBuffer<byte>(32, 0);
public CircularBuffer<byte> B = new CircularBuffer<byte>(32, 0);
public CircularBufferOverwriting<short> VisBufA = new CircularBufferOverwriting<short>(1024);
public CircularBufferOverwriting<short> VisBufB = new CircularBufferOverwriting<short>(1024);
public short CurrentValueA;
public short CurrentValueB;
public short PreviousValueA;
public short PreviousValueB;
public long LastSampleTimeA;
public long LastSampleTimeB;
public long IntervalA = 1;
public long IntervalB = 1;
uint BiasLevel = 0x100;
uint AmplitudeRes;
// SOUNDCNT_H
uint SoundVolume = 0; // 0-1
byte DmaSoundAVolume = 0; // 2
byte DmaSoundBVolume = 0; // 3
bool DmaSoundAEnableRight = false; // 8
bool DmaSoundAEnableLeft = false; // 9
bool DmaSoundATimerSelect = false; // 10
bool DmaSoundBEnableRight = false; // 11
bool DmaSoundBEnableLeft = false; // 12
bool DmaSoundBTimerSelect = false; // 13
bool MasterEnable = false;
public byte ReadHwio8(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x4000082: // SOUNDCNT_H B0
val |= (byte)((SoundVolume >> 0) & 0b11); // 0-1
val |= (byte)(DmaSoundAVolume << 2); // 2
val |= (byte)(DmaSoundBVolume << 3); // 3
break;
case 0x4000083: // SOUNDCNT_H B1
if (DmaSoundAEnableRight) val = BitSet(val, 8 - 8); // 8
if (DmaSoundAEnableLeft) val = BitSet(val, 9 - 8); // 9
if (DmaSoundATimerSelect) val = BitSet(val, 10 - 8); // 10
if (DmaSoundBEnableRight) val = BitSet(val, 12 - 8); // 12
if (DmaSoundBEnableLeft) val = BitSet(val, 13 - 8); // 13
if (DmaSoundBTimerSelect) val = BitSet(val, 14 - 8); // 14
break;
// Special case, because SOUNDCNT_X contains both GB Audio status and GBA audio status
case 0x4000084:
// NR52
byte i = 0;
i |= 0b01110000;
if (GbAudio.noise_enabled && GbAudio.noise_dacEnabled) i |= (byte)BIT_3;
if (GbAudio.wave_enabled && GbAudio.wave_dacEnabled) i |= (byte)BIT_2;
if (GbAudio.pulse2_enabled && GbAudio.pulse2_dacEnabled) i |= (byte)BIT_1;
if (GbAudio.pulse1_enabled && GbAudio.pulse1_dacEnabled) i |= (byte)BIT_0;
if (MasterEnable) i |= (byte)BIT_7;
return i;
case 0x4000088: // SOUNDBIAS B0
val |= (byte)(BiasLevel << 1);
break;
case 0x4000089: // SOUNDBIAS B1
val |= (byte)(BiasLevel >> 7);
val |= (byte)(AmplitudeRes << 6);
break;
}
if (addr >= 0x4000060 && addr <= 0x4000084)
{
// GB Registers
val = GbAudio.ReadHwio8(addr & 0xFF);
}
else if (addr >= 0x4000090 && addr <= 0x400009F)
{
// Wave RAM
val = GbAudio.ReadHwio8(addr & 0xFF);
}
return val;
}
public void WriteHwio8(uint addr, byte val)
{
if (addr >= 0x4000060 && addr <= 0x400009F)
{
GbAudio.WriteHwio8(addr & 0xFF, val);
}
switch (addr)
{
case 0x4000082: // SOUNDCNT_H B0
SoundVolume = (uint)(val & 0b11); // 0-1
DmaSoundAVolume = (byte)((val >> 2) & 1); // 2
DmaSoundBVolume = (byte)((val >> 3) & 1); // 3
break;
case 0x4000083: // SOUNDCNT_H B1
DmaSoundAEnableRight = BitTest(val, 8 - 8); // 8
DmaSoundAEnableLeft = BitTest(val, 9 - 8); // 9
DmaSoundATimerSelect = BitTest(val, 10 - 8); // 10
if (BitTest(val, 11 - 8)) A.Reset();
DmaSoundBEnableRight = BitTest(val, 12 - 8); // 12
DmaSoundBEnableLeft = BitTest(val, 13 - 8); // 13
DmaSoundBTimerSelect = BitTest(val, 14 - 8); // 14
if (BitTest(val, 15 - 8)) B.Reset();
break;
case 0x4000084: // SOUNDCNT_X
MasterEnable = BitTest(val, 7);
break;
case 0x4000088: // SOUNDBIAS B0
BiasLevel &= 0b110000000;
BiasLevel |= (uint)((val >> 1) & 0b1111111);
break;
case 0x4000089: // SOUNDBIAS B1
BiasLevel &= 0b001111111;
BiasLevel |= (uint)((val & 0b11) << 7);
AmplitudeRes &= 0;
AmplitudeRes |= (uint)((val >> 6) & 0b11);
break;
case 0x40000A0:
case 0x40000A1:
case 0x40000A2:
case 0x40000A3:
// Gba.Arm7.Error("FIFO Insert");
A.Insert(val);
break;
case 0x40000A4:
case 0x40000A5:
case 0x40000A6:
case 0x40000A7:
// Gba.Arm7.Error("FIFO Insert");
B.Insert(val);
break;
}
}
public bool CollectSamples = true;
public bool EnablePsg = true;
public bool EnableFifo = true;
public BlipBuf BlipBuf = new BlipBuf(32, true, 2);
public ResamplingMode ResamplingMode = ResamplingMode.Sinc;
public const int SampleRate = 32768;
const int CyclesPerSample = 16777216 / SampleRate;
// public CircularBuffer<short> SampleBuffer = new CircularBuffer<short>(32768, 0);
public const uint SampleBufferMax = 256;
public float[] SampleBuffer = new float[SampleBufferMax];
public uint SampleBufferPos = 0;
public bool AudioReady;
public uint VisSamplingTimer = 0;
public void Sample(long cyclesLate)
{
GbAudio.Tick(CyclesPerSample / 4); // convert to GB sample rate
BlipBuf.ReadOutSample();
double fifoASinc = BlipBuf.CurrentValL;
double fifoBSinc = BlipBuf.CurrentValR;
short fifoA = 0;
short fifoB = 0;
short psgA = 0;
short psgB = 0;
if (MasterEnable)
{
if (EnablePsg)
{
psgA += GbAudio.Out1;
psgB += GbAudio.Out2;
}
if (EnableFifo)
{
switch (ResamplingMode)
{
case ResamplingMode.None:
if (DebugEnableA)
{
if (DmaSoundAEnableLeft) fifoA += CurrentValueA;
if (DmaSoundAEnableRight) fifoB += CurrentValueA;
}
if (DebugEnableB)
{
if (DmaSoundBEnableLeft) fifoA += CurrentValueB;
if (DmaSoundBEnableRight) fifoB += CurrentValueB;
}
break;
case ResamplingMode.Linear:
long current = Scheduler.CurrentTicks - cyclesLate;
if (DebugEnableA)
{
double ratio = (current - LastSampleTimeA) / (double)IntervalA;
double valDouble = (PreviousValueA + ratio * (double)(CurrentValueA - PreviousValueA));
short val = (short)valDouble;
if (DmaSoundAEnableLeft) fifoA += val;
if (DmaSoundAEnableRight) fifoB += val;
}
if (DebugEnableB)
{
double ratio = (current - LastSampleTimeB) / (double)IntervalB;
double valDouble = (PreviousValueB + ratio * (double)(CurrentValueB - PreviousValueB));
short val = (short)valDouble;
if (DmaSoundBEnableLeft) fifoA += val;
if (DmaSoundBEnableRight) fifoB += val;
}
break;
case ResamplingMode.Sinc:
case ResamplingMode.SincLowPass:
if (DebugEnableA)
{
if (DmaSoundAEnableLeft) fifoA += (short)(fifoASinc);
if (DmaSoundAEnableRight) fifoB += (short)(fifoASinc);
}
if (DebugEnableB)
{
if (DmaSoundBEnableLeft) fifoA += (short)(fifoBSinc);
if (DmaSoundBEnableRight) fifoB += (short)(fifoBSinc);
}
break;
}
}
}
if (++VisSamplingTimer >= 4)
{
VisSamplingTimer = 0;
VisBufA.Insert(CurrentValueA);
VisBufB.Insert(CurrentValueB);
}
SampleBuffer[SampleBufferPos++] = ((fifoA + psgA) * 64f)/ SampleRate;
SampleBuffer[SampleBufferPos++] = ((fifoB + psgB) * 64f)/ SampleRate;
if (SampleBufferPos >= SampleBufferMax)
{
SampleBufferPos = 0;
Gba.AudioCallback(SampleBuffer);
}
Scheduler.AddEventRelative(SchedulerId.ApuSample, CyclesPerSample - cyclesLate, Sample);
}
public void TimerOverflowFifoA(long cyclesLate, uint timerId)
{
LastSampleTimeA = Scheduler.CurrentTicks - cyclesLate;
PreviousValueA = CurrentValueA;
IntervalA = Gba.Timers.T[timerId].Interval;
CurrentValueA = (short)((sbyte)A.Pop() << DmaSoundAVolume);
BlipBuf.SetValue(0, (float)LastSampleTimeA / CyclesPerSample, (float)CurrentValueA, 0);
if (A.Entries <= 16)
{
Gba.Dma.RepeatFifoA();
}
}
public void TimerOverflowFifoB(long cyclesLate, uint timerId)
{
LastSampleTimeB = Scheduler.CurrentTicks - cyclesLate;
PreviousValueB = CurrentValueB;
IntervalB = Gba.Timers.T[timerId].Interval;
CurrentValueB = (short)((sbyte)B.Pop() << DmaSoundBVolume);
BlipBuf.SetValue(1, (float)LastSampleTimeB / CyclesPerSample, 0, (float)CurrentValueB);
if (B.Entries <= 16)
{
Gba.Dma.RepeatFifoB();
}
}
// Called when Timer 0 or 1 overflows.
public void TimerOverflow(long cyclesLate, uint timerId)
{
if (timerId == 0)
{
if (!DmaSoundATimerSelect)
{
TimerOverflowFifoA(cyclesLate, timerId);
}
if (!DmaSoundBTimerSelect)
{
TimerOverflowFifoB(cyclesLate, timerId);
}
}
else if (timerId == 1)
{
if (DmaSoundATimerSelect)
{
TimerOverflowFifoA(cyclesLate, timerId);
}
if (DmaSoundBTimerSelect)
{
TimerOverflowFifoB(cyclesLate, timerId);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c5e3b65effa8d24da17efc42e12eb20
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
namespace OptimeGBA
{
public abstract class HwControl
{
public bool IME;
public uint IE;
public uint IF;
public bool Available;
public abstract void FlagInterrupt(uint interruptFlag);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 816d784dc01e7804ea2368ee2d1f800f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,109 @@
using System;
using static OptimeGBA.Bits;
namespace OptimeGBA
{
public enum InterruptGba
{
VBlank = 0,
HBlank = 1,
VCounterMatch = 2,
Timer0Overflow = 3,
Timer1Overflow = 4,
Timer2Overflow = 5,
Timer3Overflow = 6,
Serial = 7,
Dma0 = 8,
Dma1 = 9,
Dma2 = 10,
Dma3 = 11,
Keypad = 12,
GamePak = 13,
}
public sealed class HwControlGba : HwControl
{
Gba Gba;
public HwControlGba(Gba gba)
{
Gba = gba;
}
public byte ReadHwio8(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x4000200: // IE B0
return (byte)(IE >> 0);
case 0x4000201: // IE B1
return (byte)(IE >> 8);
case 0x4000202: // IF B0
return (byte)(IF >> 0);
case 0x4000203: // IF B1
return (byte)(IF >> 8);
case 0x4000208: // IME
if (IME) val = BitSet(val, 0);
break;
}
return val;
}
public void WriteHwio8(uint addr, byte val)
{
switch (addr)
{
case 0x4000200: // IE B0
IE &= 0x3F00;
IE |= (ushort)((ushort)val << 0);
CheckAndFireInterrupts();
break;
case 0x4000201: // IE B1
IE &= 0x00FF;
IE |= (ushort)((val & 0x3F) << 8);
CheckAndFireInterrupts();
break;
case 0x4000202: // IF B0
IF &= (ushort)(~((ushort)val << 0));
CheckAndFireInterrupts();
break;
case 0x4000203: // IF B1
IF &= (ushort)(~((val & 0x3F) << 8));
CheckAndFireInterrupts();
break;
case 0x4000208: // IME
IME = BitTest(val, 0);
CheckAndFireInterrupts();
break;
case 0x4000301: // HALTCNT
if (BitTest(val, 7))
{
}
else
{
Gba.Scheduler.AddEventRelative(SchedulerId.HaltSkip, 0, Gba.HaltSkip);
}
break;
}
}
public override void FlagInterrupt(uint i)
{
IF |= (ushort)(1 << (int)i);
CheckAndFireInterrupts();
}
public void CheckAndFireInterrupts()
{
Available = (IE & IF & 0x3FFF) != 0;
Gba.Cpu.FlagInterrupt = Available && IME;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a26fb3a3a7a8044086fc98259d6b58d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,39 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public enum GBAKeyCode
{
Start,
Select,
Left,
Right,
Up,
Down,
A,
B,
L,
R,
}
public class KeyMappingButton : Button, IPointerDownHandler, IPointerUpHandler
{
public bool pressed { private set; get; }
public GBAKeyCode keyCode { private set; get; }
protected override void Awake()
{
pressed = false;
//keyCode = System.Enum.Parse<GBAKeyCode>(gameObject.name);
keyCode = (GBAKeyCode)System.Enum.Parse(keyCode.GetType(),gameObject.name);
}
public override void OnPointerDown(PointerEventData eventData)
{
base.OnPointerDown(eventData);
pressed = true;
}
public override void OnPointerUp(PointerEventData eventData)
{
base.OnPointerUp(eventData);
pressed = false;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d8b0ab892d8e9640a62fe93c96abfb1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

45
Assets/emulator/Keypad.cs Normal file
View File

@ -0,0 +1,45 @@
using static OptimeGBA.Bits;
namespace OptimeGBA
{
public sealed class Keypad
{
public bool A;
public bool B;
public bool Start;
public bool Select;
public bool Right;
public bool Left;
public bool Up;
public bool Down;
public bool R;
public bool L;
public byte ReadHwio8(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x4000130: // KEYINPUT B0
if (!A) val = BitSet(val, 0);
if (!B) val = BitSet(val, 1);
if (!Select) val = BitSet(val, 2);
if (!Start) val = BitSet(val, 3);
if (!Right) val = BitSet(val, 4);
if (!Left) val = BitSet(val, 5);
if (!Up) val = BitSet(val, 6);
if (!Down) val = BitSet(val, 7);
break;
case 0x4000131: // KEYINPUT B1
if (!R) val = BitSet(val, 8 - 8);
if (!L) val = BitSet(val, 9 - 8);
break;
case 0x4000137: // EXTKEYIN B1
val = 0;
break;
}
return val;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 444bf15c0d0cab040b1b4967a2c3f94c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

205
Assets/emulator/Memory.cs Normal file
View File

@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static OptimeGBA.MemoryUtil;
namespace OptimeGBA
{
public unsafe abstract class Memory
{
public SaveProvider SaveProvider;
public SortedDictionary<uint, uint> HwioWriteLog = new SortedDictionary<uint, uint>();
public SortedDictionary<uint, uint> HwioReadLog = new SortedDictionary<uint, uint>();
public bool LogHwioAccesses = false;
public abstract void InitPageTable(byte*[] pageTable, uint[] maskTable, bool write);
public const int PageSize = 1024;
public uint[] MemoryRegionMasks = new uint[1048576];
public byte[] EmptyPage = new byte[PageSize];
public byte*[] PageTableRead = new byte*[1048576];
public byte*[] PageTableWrite = new byte*[1048576];
public abstract byte Read8Unregistered(bool debug, uint addr);
public abstract void Write8Unregistered(bool debug, uint addr, byte val);
public abstract ushort Read16Unregistered(bool debug, uint addr);
public abstract void Write16Unregistered(bool debug, uint addr, ushort val);
public abstract uint Read32Unregistered(bool debug, uint addr);
public abstract void Write32Unregistered(bool debug, uint addr, uint val);
public Dictionary<byte[], GCHandle> Handles = new Dictionary<byte[], GCHandle>();
public byte* TryPinByteArray(byte[] arr)
{
GCHandle handle;
if (!Handles.TryGetValue(arr, out handle))
{
handle = GCHandle.Alloc(arr, GCHandleType.Pinned);
Handles[arr] = handle;
}
return (byte*)handle.AddrOfPinnedObject();
}
public void UnpinByteArray(byte[] arr)
{
Handles[arr].Free();
if (!Handles.Remove(arr))
{
throw new ArgumentException("Tried to unpin already unpinned array.");
}
}
public void InitPageTables()
{
InitPageTable(PageTableRead, MemoryRegionMasks, false);
InitPageTable(PageTableWrite, MemoryRegionMasks, true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint MaskAddress(uint addr)
{
return addr & MemoryRegionMasks[addr >> 12];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* ResolvePageRead(uint addr)
{
return PageTableRead[addr >> 12];
}
public byte* ResolvePageWrite(uint addr)
{
return PageTableWrite[addr >> 12];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte Read8(uint addr)
{
var page = ResolvePageRead(addr);
if (page != null)
{
return GetByte(page, MaskAddress(addr));
}
return Read8Unregistered(false, addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort Read16(uint addr)
{
#if DEBUG
if ((addr & 1) != 0)
{
Console.Error.WriteLine("Misaligned Read16! " + Util.HexN(addr, 8));
}
#endif
var page = ResolvePageRead(addr);
if (page != null)
{
return GetUshort(page, MaskAddress(addr));
}
return Read16Unregistered(false, addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadDebug16(uint addr)
{
var page = ResolvePageRead(addr);
if (page != null)
{
return GetUshort(page, MaskAddress(addr));
}
return Read16Unregistered(true, addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint Read32(uint addr)
{
#if DEBUG
if ((addr & 3) != 0)
{
Console.Error.WriteLine("Misaligned Read32! " + Util.HexN(addr, 8));
}
#endif
var page = ResolvePageRead(addr);
if (page != null)
{
return GetUint(page, MaskAddress(addr));
}
return Read32Unregistered(false, addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadDebug32(uint addr)
{
var page = ResolvePageRead(addr);
if (page != null)
{
return GetUint(page, MaskAddress(addr));
}
return Read32Unregistered(true, addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write8(uint addr, byte val)
{
var page = ResolvePageWrite(addr);
if (page != null)
{
SetByte(page, MaskAddress(addr), val);
return;
}
Write8Unregistered(false, addr, val);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write16(uint addr, ushort val)
{
#if DEBUG
if ((addr & 1) != 0)
{
Console.Error.WriteLine("Misaligned Write16! " + Util.HexN(addr, 8));
}
#endif
var page = ResolvePageWrite(addr);
if (page != null)
{
SetUshort(page, MaskAddress(addr), val);
return;
}
Write16Unregistered(false, addr, val);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write32(uint addr, uint val)
{
#if DEBUG
if ((addr & 3) != 0)
{
Console.Error.WriteLine("Misaligned Write32! " + Util.HexN(addr, 8));
}
#endif
var page = ResolvePageWrite(addr);
if (page != null)
{
SetUint(page, MaskAddress(addr), val);
return;
}
Write32Unregistered(false, addr, val);
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c63b68705a3d713459aaacd1a2506ed8
guid: a210b172849cb384e8d6c0a0d4956259
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,438 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Collections.Concurrent;
using static OptimeGBA.Bits;
using System.Runtime.InteropServices;
using static OptimeGBA.MemoryUtil;
namespace OptimeGBA
{
public sealed unsafe class MemoryGba : Memory
{
Gba Gba;
public MemoryGba(Gba gba, ProviderGba provider)
{
Gba = gba;
for (uint i = 0; i < MaxRomSize && i < provider.Rom.Length; i++)
{
Rom[i] = provider.Rom[i];
}
for (uint i = 0; i < BiosSize && i < provider.Bios.Length; i++)
{
Bios[i] = provider.Bios[i];
}
RomSize = (uint)provider.Rom.Length;
// Detect save type
string[] strings = {
"NONE_LOLOLLEXTRATONOTMATCHRANDOMSTRINGS",
"EEPROM_",
"SRAM_",
"FLASH_",
"FLASH512_",
"FLASH1M_",
};
uint matchedIndex = 0;
for (uint i = 0; i < strings.Length; i++)
{
char[] chars = strings[i].ToCharArray();
int stringLength = chars.Length;
int matchLength = 0;
for (uint j = 0; j < provider.Rom.Length; j++)
{
if (provider.Rom[j] == chars[matchLength])
{
matchLength++;
if (matchLength >= chars.Length)
{
matchedIndex = i;
goto breakOuterLoop;
}
}
else
{
matchLength = 0;
}
}
}
breakOuterLoop:
//Debug.Log($"Save Type: {strings[matchedIndex]}");
switch (matchedIndex)
{
case 0: SaveProvider = new NullSaveProvider(); break;
case 1:
SaveProvider = new Eeprom(Gba, EepromSize.Eeprom64k);
if (RomSize < 16777216)
{
EepromThreshold = 0x1000000;
}
else
{
EepromThreshold = 0x1FFFF00;
}
//Debug.Log("EEPROM Threshold: " + Util.Hex(EepromThreshold, 8));
break;
case 2: SaveProvider = new Sram(); break;
case 3: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break;
case 4: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break;
case 5: SaveProvider = new Flash(Gba, FlashSize.Flash1m); break;
}
}
public uint EepromThreshold = 0x2000000;
public const int BiosSize = 16384;
public const int MaxRomSize = 67108864;
public const int EwramSize = 262144;
public const int IwramSize = 32768;
public uint RomSize;
public byte[] Bios = new byte[BiosSize];
public byte[] Rom = new byte[MaxRomSize];
public byte[] Ewram = new byte[EwramSize];
public byte[] Iwram = new byte[IwramSize];
public override void InitPageTable(byte*[] table, uint[] maskTable, bool write)
{
byte* bios = TryPinByteArray(Bios);
byte* ewram = TryPinByteArray(Ewram);
byte* iwram = TryPinByteArray(Iwram);
byte* palettes = TryPinByteArray(Gba.Ppu.Renderer.Palettes);
byte* vram = TryPinByteArray(Gba.Ppu.Vram);
byte* emptyPage = TryPinByteArray(EmptyPage);
byte* oam = TryPinByteArray(Gba.Ppu.Renderer.Oam);
byte* rom = TryPinByteArray(Rom);
// 12 bits shaved off already, shave off another 12 to get 24
for (uint i = 0; i < 1048576; i++)
{
uint addr = (uint)(i << 12);
switch (i >> 12)
{
case 0x0: // BIOS
if (!write)
{
table[i] = bios;
}
maskTable[i] = 0x00003FFF;
break;
case 0x2: // EWRAM
table[i] = ewram;
maskTable[i] = 0x0003FFFF;
break;
case 0x3: // IWRAM
table[i] = iwram;
maskTable[i] = 0x00007FFF;
break;
case 0x5: // Palettes
if (!write)
{
table[i] = palettes;
}
maskTable[i] = 0x3FF;
break;
case 0x6: // PPU VRAM
addr &= 0x1FFFF;
if (addr < 0x18000)
{
table[i] = vram;
}
else
{
table[i] = emptyPage;
}
maskTable[i] = 0x0001FFFF; // VRAM
break;
case 0x7: // PPU OAM
table[i] = oam;
maskTable[i] = 0x000003FF;
break;
case 0x8: // Game Pak ROM/FlashROM
case 0x9: // Game Pak ROM/FlashROM
case 0xA: // Game Pak ROM/FlashROM
case 0xB: // Game Pak ROM/FlashROM
case 0xC: // Game Pak ROM/FlashROM
if (!write)
{
table[i] = rom;
}
maskTable[i] = 0x01FFFFFF;
break;
case 0xD: // Game Pak ROM/FlashROM/EEPROM
maskTable[i] = 0x01FFFFFF;
break;
}
}
}
~MemoryGba()
{
//Debug.Log("Cleaning up GBA memory...");
UnpinByteArray(Bios);
UnpinByteArray(Ewram);
UnpinByteArray(Iwram);
UnpinByteArray(Gba.Ppu.Renderer.Palettes);
UnpinByteArray(Gba.Ppu.Vram);
UnpinByteArray(EmptyPage);
UnpinByteArray(Gba.Ppu.Renderer.Oam);
UnpinByteArray(Rom);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override byte Read8Unregistered(bool debug, uint addr)
{
switch (addr >> 24)
{
case 0x4: // I/O Registers
// addr &= 0x400FFFF;
return ReadHwio8(debug, addr);
case 0xA: // ROM / EEPROM
case 0xB: // ROM / EEPROM
case 0xC: // ROM / EEPROM
case 0xD: // ROM / EEPROM
uint adjAddr = addr & 0x1FFFFFF;
if (adjAddr >= EepromThreshold)
{
return SaveProvider.Read8(adjAddr);
}
return GetByte(Rom, adjAddr);
case 0xE: // Game Pak SRAM/Flash
case 0xF: // Game Pak SRAM/Flash
return SaveProvider.Read8(addr);
}
return 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override ushort Read16Unregistered(bool debug, uint addr)
{
switch (addr >> 24)
{
case 0x4: // I/O Registers
byte f0 = Read8Unregistered(debug, addr++);
byte f1 = Read8Unregistered(debug, addr++);
ushort u16 = (ushort)((f1 << 8) | (f0 << 0));
return u16;
case 0xA: // ROM / EEPROM
case 0xB: // ROM / EEPROM
case 0xC: // ROM / EEPROM
case 0xD: // ROM / EEPROM
uint adjAddr = addr & 0x1FFFFFF;
if (adjAddr >= EepromThreshold)
{
return SaveProvider.Read8(adjAddr);
}
return GetUshort(Rom, adjAddr);
}
return 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override uint Read32Unregistered(bool debug, uint addr)
{
switch (addr >> 24)
{
case 0x4: // I/O Registers
byte f0 = ReadHwio8(debug, addr++);
byte f1 = ReadHwio8(debug, addr++);
byte f2 = ReadHwio8(debug, addr++);
byte f3 = ReadHwio8(debug, addr++);
uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0));
return u32;
}
return 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Write8Unregistered(bool debug, uint addr, byte val)
{
switch (addr >> 24)
{
case 0x4: // I/O Registers
// addr &= 0x400FFFF;
WriteHwio8(debug, addr, val);
break;
case 0xA: // ROM / EEPROM
case 0xB: // ROM / EEPROM
case 0xC: // ROM / EEPROM
case 0xD: // ROM / EEPROM
uint adjAddr = addr & 0x1FFFFFF;
if (adjAddr >= EepromThreshold)
{
SaveProvider.Write8(adjAddr, val);
}
break;
case 0xE: // Game Pak SRAM/Flash
case 0xF: // Game Pak SRAM/Flash
SaveProvider.Write8(addr, val);
return;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Write16Unregistered(bool debug, uint addr, ushort val)
{
switch (addr >> 24)
{
case 0x4: // I/O Registers
WriteHwio8(debug, addr++, (byte)(val >> 0));
WriteHwio8(debug, addr++, (byte)(val >> 8));
break;
case 0x5: // PPU Palettes
addr &= 0x3FF;
if (GetUshort(Gba.Ppu.Renderer.Palettes, addr) != val)
{
SetUshort(Gba.Ppu.Renderer.Palettes, addr, val);
}
break;
case 0xA: // ROM / EEPROM
case 0xB: // ROM / EEPROM
case 0xC: // ROM / EEPROM
case 0xD: // ROM / EEPROM
uint adjAddr = addr & 0x1FFFFFF;
if (adjAddr >= EepromThreshold)
{
SaveProvider.Write8(adjAddr, (byte)val);
}
break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Write32Unregistered(bool debug, uint addr, uint val)
{
switch (addr >> 24)
{
case 0x4: // I/O Registers
WriteHwio8(debug, addr++, (byte)(val >> 0));
WriteHwio8(debug, addr++, (byte)(val >> 8));
WriteHwio8(debug, addr++, (byte)(val >> 16));
WriteHwio8(debug, addr++, (byte)(val >> 24));
break;
case 0x5: // PPU Palettes
addr &= 0x3FF;
if (GetUint(Gba.Ppu.Renderer.Palettes, addr) != val)
{
SetUint(Gba.Ppu.Renderer.Palettes, addr, val);
}
return;
case 0x6: // PPU VRAM
addr &= 0x1FFFF;
if (addr < 0x18000)
{
SetUint(Gba.Ppu.Vram, addr, val);
}
return;
}
}
public byte ReadHwio8(bool debug, uint addr)
{
if (LogHwioAccesses && (addr & ~1) != 0 && !debug)
{
uint count;
HwioWriteLog.TryGetValue(addr, out count);
HwioWriteLog[addr] = count + 1;
}
if (addr >= 0x4000000 && addr <= 0x4000056) // PPU
{
return Gba.Ppu.ReadHwio8(addr);
}
else if (addr >= 0x4000060 && addr <= 0x40000A8) // Sound
{
return Gba.GbaAudio.ReadHwio8(addr);
}
else if (addr >= 0x40000B0 && addr <= 0x40000DF) // DMA
{
return Gba.Dma.ReadHwio8(addr);
}
else if (addr >= 0x4000100 && addr <= 0x400010F) // Timer
{
return Gba.Timers.ReadHwio8(addr);
}
else if (addr >= 0x4000120 && addr <= 0x400012C) // Serial
{
}
else if (addr >= 0x4000130 && addr <= 0x4000132) // Keypad
{
return Gba.Keypad.ReadHwio8(addr);
}
else if (addr >= 0x4000134 && addr <= 0x400015A) // Serial Communications
{
switch (addr) {
case 0x4000135: return 0x80;
}
}
else if (addr >= 0x4000200 && addr <= 0x4FF0800) // Interrupt, Waitstate, and Power-Down Control
{
return Gba.HwControl.ReadHwio8(addr);
}
return 0;
}
public void WriteHwio8(bool debug, uint addr, byte val)
{
if (LogHwioAccesses && (addr & ~1) != 0 && !debug)
{
uint count;
HwioReadLog.TryGetValue(addr, out count);
HwioReadLog[addr] = count + 1;
}
if (addr >= 0x4000000 && addr <= 0x4000056) // PPU
{
Gba.Ppu.WriteHwio8(addr, val);
}
else if (addr >= 0x4000060 && addr <= 0x40000A7) // Sound
{
Gba.GbaAudio.WriteHwio8(addr, val);
}
else if (addr >= 0x40000B0 && addr <= 0x40000DF) // DMA
{
Gba.Dma.WriteHwio8(addr, val);
}
else if (addr >= 0x4000100 && addr <= 0x400010F) // Timer
{
Gba.Timers.WriteHwio8(addr, val);
}
else if (addr >= 0x4000120 && addr <= 0x400012C) // Serial
{
}
else if (addr >= 0x4000130 && addr <= 0x4000132) // Keypad
{
}
else if (addr >= 0x4000134 && addr <= 0x400015A) // Serial Communications
{
}
else if (addr >= 0x4000200 && addr <= 0x4FF0800) // Interrupt, Waitstate, and Power-Down Control
{
Gba.HwControl.WriteHwio8(addr, val);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a6b84548f6fe89488daa4de20cb9164
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Collections.Concurrent;
using static OptimeGBA.Bits;
using System.Runtime.InteropServices;
namespace OptimeGBA
{
public static unsafe class MemoryUtil
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong GetUlong(byte[] arr, uint addr)
{
return
((ulong)arr[addr + 0] << 0) |
((ulong)arr[addr + 1] << 8) |
((ulong)arr[addr + 2] << 16) |
((ulong)arr[addr + 3] << 24) |
((ulong)arr[addr + 4] << 32) |
((ulong)arr[addr + 5] << 40) |
((ulong)arr[addr + 6] << 48) |
((ulong)arr[addr + 7] << 56);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint GetUint(byte[] arr, uint addr)
{
return (uint)(
(arr[addr + 0] << 0) |
(arr[addr + 1] << 8) |
(arr[addr + 2] << 16) |
(arr[addr + 3] << 24)
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort GetUshort(byte[] arr, uint addr)
{
return (ushort)(
(arr[addr + 0] << 0) |
(arr[addr + 1] << 8)
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByte(byte[] arr, uint addr)
{
return arr[addr];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong GetUlong(byte* arr, uint addr)
{
return *(ulong*)(arr + addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint GetUint(byte* arr, uint addr)
{
return *(uint*)(arr + addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort GetUshort(byte* arr, uint addr)
{
return *(ushort*)(arr + addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByte(byte* arr, uint addr)
{
return *(byte*)(arr + addr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetUlong(byte[] arr, uint addr, ulong val)
{
arr[addr + 0] = (byte)(val >> 0);
arr[addr + 1] = (byte)(val >> 8);
arr[addr + 2] = (byte)(val >> 16);
arr[addr + 3] = (byte)(val >> 24);
arr[addr + 4] = (byte)(val >> 32);
arr[addr + 5] = (byte)(val >> 40);
arr[addr + 6] = (byte)(val >> 48);
arr[addr + 7] = (byte)(val >> 56);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetUint(byte[] arr, uint addr, uint val)
{
arr[addr + 0] = (byte)(val >> 0);
arr[addr + 1] = (byte)(val >> 8);
arr[addr + 2] = (byte)(val >> 16);
arr[addr + 3] = (byte)(val >> 24);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetUshort(byte[] arr, uint addr, ushort val)
{
arr[addr + 0] = (byte)(val >> 0);
arr[addr + 1] = (byte)(val >> 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetByte(byte[] arr, uint addr, byte val)
{
arr[addr] = val;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetUlong(byte* arr, uint addr, ulong val)
{
*(ulong*)(arr + addr) = val;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetUint(byte* arr, uint addr, uint val)
{
*(uint*)(arr + addr) = val;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetUshort(byte* arr, uint addr, ushort val)
{
*(ushort*)(arr + addr) = val;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetByte(byte* arr, uint addr, byte val)
{
*(byte*)(arr + addr) = val;
}
public static byte* AllocateUnmanagedArray(int size)
{
byte* arr = (byte*)Marshal.AllocHGlobal(size).ToPointer();
// Zero out array
for (int i = 0; i < size; i++)
{
arr[i] = 0;
}
return arr;
}
public static ushort* AllocateUnmanagedArray16(int size)
{
ushort* arr = (ushort*)Marshal.AllocHGlobal(size * sizeof(ushort)).ToPointer();
// Zero out array
for (int i = 0; i < size; i++)
{
arr[i] = 0;
}
return arr;
}
public static uint* AllocateUnmanagedArray32(int size)
{
uint* arr = (uint*)Marshal.AllocHGlobal(size * sizeof(uint)).ToPointer();
// Zero out array
for (int i = 0; i < size; i++)
{
arr[i] = 0;
}
return arr;
}
public static void FreeUnmanagedArray(void* arr)
{
Marshal.FreeHGlobal(new IntPtr(arr));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 13e3364ce9f87224fa1b1bfac909d236
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.IO;
public class PipeStream : Stream
{
private readonly Queue<byte> _buffer = new Queue<byte>();
private long _maxBufferLength = 8192*4;
public long MaxBufferLength
{
get { return _maxBufferLength; }
set { _maxBufferLength = value; }
}
public new void Dispose()
{
_buffer.Clear();
}
public override void Flush()
{
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (offset != 0)
throw new NotImplementedException("Offsets with value of non-zero are not supported");
if (buffer == null)
throw new ArgumentException("Buffer is null");
if (offset + count > buffer.Length)
throw new ArgumentException("The sum of offset and count is greater than the buffer length. ");
if (offset < 0 || count < 0)
throw new ArgumentOutOfRangeException("offset", "offset or count is negative.");
if (count == 0)
return 0;
int readLength = 0;
lock (_buffer)
{
// fill the read buffer
for (; readLength < count && Length > 0; readLength++)
{
buffer[readLength] = _buffer.Dequeue();
}
}
return readLength;
}
private bool ReadAvailable(int count)
{
return (Length >= count);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentException("Buffer is null");
if (offset + count > buffer.Length)
throw new ArgumentException("The sum of offset and count is greater than the buffer length. ");
if (offset < 0 || count < 0)
throw new ArgumentOutOfRangeException("offset", "offset or count is negative.");
if (count == 0)
return;
lock (_buffer)
{
while (Length >= _maxBufferLength)
return;
// queue up the buffer data
foreach (byte b in buffer)
{
_buffer.Enqueue(b);
}
}
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { return _buffer.Count; }
}
public override long Position
{
get { return 0; }
set { throw new NotImplementedException(); }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 177ba151dcfeb3046a3b7a0db12fec0b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

261
Assets/emulator/Ppu.cs Normal file
View File

@ -0,0 +1,261 @@
using static OptimeGBA.Bits;
using System.Runtime.InteropServices;
namespace OptimeGBA
{
public enum BackgroundMode
{
Char,
Display3D,
Affine,
Extended,
Large,
// Extended
Affine16BitBgMapEntries,
Affine256ColorBitmap,
AffineFullColorBitmap,
}
public sealed class Background
{
bool Nds;
public Background(bool nds, byte id)
{
Nds = nds;
Id = id;
}
byte[] BGCNTValue = new byte[2];
// BGCNT
public byte Priority = 0;
public uint CharBaseBlock = 0;
public bool EnableMosaic = false;
public bool Use8BitColor = false;
public uint MapBaseBlock = 0;
public bool OverflowWrap = false;
public uint ScreenSize = 0;
// BGCNT NDS
public bool AffineBitmap;
public bool AffineBitmapFullColor;
// BGH/VOFS
public uint HorizontalOffset;
public uint VerticalOffset;
public byte Id;
public uint RefPointX;
public uint RefPointY;
public short AffineA;
public short AffineB;
public short AffineC;
public short AffineD;
public int AffinePosX;
public int AffinePosY;
// Set by PrepareBackgroundAndWindow() and used by RenderBgModes()
public BackgroundMode Mode;
public byte ReadBGCNT(uint addr)
{
switch (addr)
{
case 0x00: // BGCNT B0
return BGCNTValue[0];
case 0x01: // BGCNT B1
return BGCNTValue[1];
}
return 0;
}
public void WriteBGCNT(uint addr, byte val)
{
switch (addr)
{
case 0x00: // BGCNT B0
Priority = (byte)((val >> 0) & 0b11);
// These bits overlay other bits on NDS
AffineBitmap = BitTest(val, 7);
AffineBitmapFullColor = BitTest(val, 2);
EnableMosaic = BitTest(val, 6);
if (!Nds)
{
CharBaseBlock = (uint)(val >> 2) & 0b11;
}
else
{
CharBaseBlock = (uint)(val >> 2) & 0b1111;
}
EnableMosaic = BitTest(val, 6);
Use8BitColor = BitTest(val, 7);
BGCNTValue[0] = val;
break;
case 0x01: // BGCNT B1
MapBaseBlock = (uint)(val >> 0) & 0b11111;
OverflowWrap = BitTest(val, 5);
ScreenSize = (uint)(val >> 6) & 0b11;
BGCNTValue[1] = val;
break;
}
}
public void WriteBGOFS(uint addr, byte val)
{
switch (addr)
{
case 0x0: // BGHOFS B0
HorizontalOffset &= ~0x0FFu;
HorizontalOffset |= (uint)((val << 0) & 0x0FFu);
break;
case 0x1: // BGHOFS B1
HorizontalOffset &= ~0x100u;
HorizontalOffset |= (uint)((val << 8) & 0x100u);
break;
case 0x2: // BGVOFS B0
VerticalOffset &= ~0x0FFu;
VerticalOffset |= (uint)((val << 0) & 0x0FFu);
break;
case 0x3: // BGVOFS B1
VerticalOffset &= ~0x100u;
VerticalOffset |= (uint)((val << 8) & 0x100u);
break;
}
}
public void WriteBGXY(uint addr, byte val)
{
byte offset = (byte)((addr & 3) * 8);
switch (addr)
{
case 0x0: // BGX_L
case 0x1: // BGX_L
case 0x2: // BGX_H
case 0x3: // BGX_H
RefPointX &= ~(0xFFu << offset);
RefPointX |= (uint)(val << offset);
break;
case 0x4: // BGY_L
case 0x5: // BGY_L
case 0x6: // BGY_H
case 0x7: // BGY_H
RefPointY &= ~(0xFFu << offset);
RefPointY |= (uint)(val << offset);
break;
}
CopyAffineParams();
}
public void CopyAffineParams()
{
// also sign extend
AffinePosX = ((int)RefPointX << 4) >> 4;
AffinePosY = ((int)RefPointY << 4) >> 4;
}
public void WriteBGPX(uint addr, byte val)
{
byte offset = (byte)((addr & 1) * 8);
switch (addr)
{
case 0x0: // BGPA B0
case 0x1: // BGPA B1
AffineA &= (short)~(0xFFu << offset);
AffineA |= (short)(val << offset);
break;
case 0x2: // BGPB B0
case 0x3: // BGPB B1
AffineB &= (short)~(0xFFu << offset);
AffineB |= (short)(val << offset);
break;
case 0x4: // BGPC B0
case 0x5: // BGPC B1
AffineC &= (short)~(0xFFu << offset);
AffineC |= (short)(val << offset);
break;
case 0x6: // BGPD B0
case 0x7: // BGPD B1
AffineD &= (short)~(0xFFu << offset);
AffineD |= (short)(val << offset);
break;
}
}
// Metadata used for rendering
public ushort GetMeta()
{
return (ushort)((Priority << 8) | (1 << Id));
}
}
[StructLayout(LayoutKind.Sequential, Size = 4)]
public struct ObjPixel
{
public ushort Color;
public byte PaletteIndex;
public byte Priority;
public ObjMode Mode;
public ObjPixel(ushort color, byte paletteIndex, byte priority, ObjMode transparent)
{
Color = color;
PaletteIndex = paletteIndex;
Priority = priority;
Mode = transparent;
}
}
public enum ObjShape
{
Square = 0,
Horizontal = 1,
Vertical = 2,
}
public enum ObjMode : byte
{
Normal = 0,
Translucent = 1,
ObjWindow = 2,
}
public enum BlendEffect
{
None = 0,
Blend = 1,
Lighten = 2,
Darken = 3,
}
public enum BlendFlag : byte
{
Bg0 = 1 << 0,
Bg1 = 1 << 1,
Bg2 = 1 << 2,
Bg3 = 1 << 3,
Obj = 1 << 4,
Backdrop = 1 << 5,
}
public enum WindowFlag : byte
{
Bg0 = 1 << 0,
Bg1 = 1 << 1,
Bg2 = 1 << 2,
Bg3 = 1 << 3,
Obj = 1 << 4,
ColorMath = 1 << 5,
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 11d62ad7b3245cc4bbce8635df978afc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

296
Assets/emulator/PpuGba.cs Normal file
View File

@ -0,0 +1,296 @@
using static OptimeGBA.Bits;
using System.Runtime.CompilerServices;
using static OptimeGBA.PpuRenderer;
using System;
namespace OptimeGBA
{
public sealed unsafe class PpuGba
{
Gba Gba;
Scheduler Scheduler;
public PpuRenderer Renderer;
public PpuGba(Gba gba, Scheduler scheduler)
{
Gba = gba;
Scheduler = scheduler;
Renderer = new PpuRenderer(240, 160);
Scheduler.AddEventRelative(SchedulerId.Ppu, 960, EndDrawingToHblank);
/*
*/
if (Gba.Provider.BootBios)
{
PrideMode = true;
// 250 frames
Scheduler.AddEventRelative(SchedulerId.None, 70224000, DisablePrideMode);
// 120 frames
Scheduler.AddEventRelative(SchedulerId.None, 33707520, EnablePrideModeLayer2);
}
}
public byte[] Vram = new byte[98304];
public long ScanlineStartCycles;
public bool PrideMode = false;
public bool PrideModeLayer2 = false;
public ushort DISPCNTValue;
// DISPSTAT
public bool VCounterMatch;
public bool VBlankIrqEnable;
public bool HBlankIrqEnable;
public bool VCounterIrqEnable;
public byte VCountSetting;
// State
public uint VCount;
public void DisablePrideMode(long cyclesLate)
{
PrideMode = false;
}
public void EnablePrideModeLayer2(long cyclesLate)
{
PrideModeLayer2 = true;
}
public long GetScanlineCycles()
{
return Scheduler.CurrentTicks - ScanlineStartCycles;
}
public byte ReadHwio8(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x4000000: // DISPCNT B0
return (byte)(DISPCNTValue >> 0);
case 0x4000001: // DISPCNT B1
return (byte)(DISPCNTValue >> 8);
case 0x4000004: // DISPSTAT B0
// Vblank flag is set in scanlines 160-226, not including 227 for some reason
if (VCount >= 160 && VCount <= 226) val = BitSet(val, 0);
// Hblank flag is set at cycle 1006, not cycle 960
if (GetScanlineCycles() >= 1006) val = BitSet(val, 1);
if (VCounterMatch) val = BitSet(val, 2);
if (VBlankIrqEnable) val = BitSet(val, 3);
if (HBlankIrqEnable) val = BitSet(val, 4);
if (VCounterIrqEnable) val = BitSet(val, 5);
break;
case 0x4000005: // DISPSTAT B1
val |= VCountSetting;
break;
case 0x4000006: // VCOUNT B0 - B1 only exists for Nintendo DS
val |= (byte)VCount;
break;
case 0x4000007:
return 0;
default:
return Renderer.ReadHwio8(addr & 0xFF);
}
return val;
}
public void WriteHwio8(uint addr, byte val)
{
switch (addr)
{
case 0x4000000: // DISPCNT B0
Renderer.BgMode = (uint)(val & 0b111);
Renderer.CgbMode = BitTest(val, 3);
Renderer.DisplayFrameSelect = BitTest(val, 4);
Renderer.HBlankIntervalFree = BitTest(val, 5);
Renderer.ObjCharOneDimensional = BitTest(val, 6);
Renderer.ForcedBlank = BitTest(val, 7);
DISPCNTValue &= 0xFF00;
DISPCNTValue |= (ushort)(val << 0);
Renderer.BackgroundSettingsDirty = true;
return;
case 0x4000001: // DISPCNT B1
Renderer.ScreenDisplayBg[0] = BitTest(val, 8 - 8);
Renderer.ScreenDisplayBg[1] = BitTest(val, 9 - 8);
Renderer.ScreenDisplayBg[2] = BitTest(val, 10 - 8);
Renderer.ScreenDisplayBg[3] = BitTest(val, 11 - 8);
Renderer.ScreenDisplayObj = BitTest(val, 12 - 8);
Renderer.Window0DisplayFlag = BitTest(val, 13 - 8);
Renderer.Window1DisplayFlag = BitTest(val, 14 - 8);
Renderer.ObjWindowDisplayFlag = BitTest(val, 15 - 8);
Renderer.AnyWindowEnabled = (val & 0b11100000) != 0;
DISPCNTValue &= 0x00FF;
DISPCNTValue |= (ushort)(val << 8);
Renderer.BackgroundSettingsDirty = true;
return;
case 0x4000004: // DISPSTAT B0
VBlankIrqEnable = BitTest(val, 3);
HBlankIrqEnable = BitTest(val, 4);
VCounterIrqEnable = BitTest(val, 5);
return;
case 0x4000005: // DISPSTAT B1
VCountSetting = val;
return;
default:
Renderer.WriteHwio8(addr & 0xFF, val);
return;
}
}
public void EndDrawingToHblank(long cyclesLate)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 272 - cyclesLate, EndHblank);
if (HBlankIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.HBlank);
}
if (Renderer.DebugEnableRendering)
{
Renderer.RenderScanlineGba(VCount, Vram);
}
Renderer.IncrementMosaicCounters();
Gba.Dma.RepeatHblank();
}
public void EndVblankToHblank(long cyclesLate)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 272 - cyclesLate, EndHblank);
if (HBlankIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.HBlank);
}
}
public void EndHblank(long cyclesLate)
{
ScanlineStartCycles = Scheduler.CurrentTicks;
if (VCount != 227)
{
VCount++;
if (VCount > 159)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndVblankToHblank);
if (VCount == 160)
{
#if DS_RESOLUTION
while (VCount < HEIGHT) {
RenderScanline();
Renderer.IncrementMosaicCounters();
VCount++;
}
VCount = 160;
#endif
Gba.Dma.RepeatVblank();
Renderer.RunVblankOperations();
if (VBlankIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.VBlank);
}
Renderer.TotalFrames++;
if (Renderer.DebugEnableRendering) Renderer.SwapBuffers();
Renderer.RenderingDone = true;
}
}
else
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndDrawingToHblank);
}
}
else
{
if (PrideMode)
{
uint objE0 = 8 * 3;
uint objE1 = 8 * 19;
uint objM0 = 8 * 4;
uint objM1 = 8 * 20;
for (uint i = 0; i < 6; i++)
{
Renderer.Oam[objE0++] = 0;
Renderer.Oam[objE1++] = 0;
}
Renderer.Oam[objM0 + 4] = 68;
Renderer.Oam[objM0 + 5] |= 2;
if (PrideModeLayer2)
{
Renderer.Oam[objM1 + 4] = 68;
Renderer.Oam[objM1 + 5] |= 2;
}
}
VCount = 0;
VCounterMatch = VCount == VCountSetting;
if (VCounterMatch && VCounterIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.VCounterMatch);
}
Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndDrawingToHblank);
// Pre-render sprites for line zero
fixed (byte* vram = Vram)
{
if (Renderer.DebugEnableObj && Renderer.ScreenDisplayObj) Renderer.RenderObjs(0, vram);
}
}
VCounterMatch = VCount == VCountSetting;
if (VCounterMatch && VCounterIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.VCounterMatch);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ebff50650dda02d42a9df8547d689656
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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