基本可用
This commit is contained in:
parent
3af171bdc3
commit
8d0487be8b
994
Assets/Game.unity
Normal file
994
Assets/Game.unity
Normal 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
|
7
Assets/Game.unity.meta
Normal file
7
Assets/Game.unity.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88a72b3d9c54eff4e991fdae233d3452
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,344 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
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)
|
||||
{
|
||||
// 确保旋转位数在有效范围内(对于uint,0到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)
|
||||
{
|
||||
// 确保旋转位数在有效范围内(对于ulong,0到63)
|
||||
count &= 63;
|
||||
|
||||
// 使用位移和位或操作来实现旋转
|
||||
// 注意:ulong需要64位操作
|
||||
ulong rightShifted = value >> count;
|
||||
ulong leftShifted = (value << (64 - count)) & 0xFFFFFFFFFFFFFFFF; // 同样,对于ulong,& 0xFFFFFFFFFFFFFFFF是多余的,但保留以增加清晰性
|
||||
|
||||
// 组合结果
|
||||
return rightShifted | leftShifted;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static class MyStruct
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vector256<T> where T : struct, IComparable, IComparable<T>, IEquatable<T>, IFormattable
|
||||
{
|
||||
public T[] data;
|
||||
|
||||
// 假设Vector256总是包含8个元素(对于uint来说,总共32字节)
|
||||
public const int ElementCount = 8;
|
||||
|
||||
// 私有构造函数,用于内部创建实例
|
||||
public Vector256(T[] data)
|
||||
{
|
||||
if (data == null || data.Length != ElementCount)
|
||||
throw new ArgumentException("Data array must contain exactly 8 elements.");
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
// 静态Create方法,用于从T类型的数组创建Vector256实例
|
||||
public static Vector256<T> Create(params T[] values)
|
||||
{
|
||||
if (values.Length > ElementCount)
|
||||
throw new ArgumentException("Too many values provided.");
|
||||
|
||||
T[] fullData = new T[ElementCount];
|
||||
Array.Copy(values, 0, fullData, 0, values.Length);
|
||||
|
||||
// 对于uint,默认值是0
|
||||
if (typeof(T) == typeof(uint))
|
||||
{
|
||||
for (int i = values.Length; i < ElementCount; i++)
|
||||
{
|
||||
fullData[i] = (T)(object)0u; // 使用显式类型转换来避免编译时错误
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector256<T>(fullData);
|
||||
}
|
||||
|
||||
// 静态Zero属性,返回一个所有元素都为0的Vector256<T>实例
|
||||
public static Vector256<T> Zero
|
||||
{
|
||||
get
|
||||
{
|
||||
if (typeof(T) == typeof(uint))
|
||||
{
|
||||
uint[] zeroData = new uint[ElementCount];
|
||||
//Array.Fill(zeroData, 0u); // 使用Array.Fill来填充所有元素为0
|
||||
return new Vector256<T>((T[])(object)zeroData); // 显式类型转换以绕过泛型约束
|
||||
}
|
||||
|
||||
// 如果T不是uint,这里可能需要抛出一个异常或者返回一个默认构造的Vector256<T>
|
||||
// 但由于泛型约束,实际上T必须是uint(或者同时满足其他接口的类型,但在这个上下文中我们只关心uint)
|
||||
// 因此,这里实际上不会执行到
|
||||
throw new InvalidOperationException("Zero property is only valid for Vector256<uint>.");
|
||||
}
|
||||
}
|
||||
|
||||
// ... 可以根据需要添加更多重载
|
||||
|
||||
// 为了方便调试,可以重写ToString方法
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join(", ", Array.ConvertAll(data, x => x.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 模拟 Avx2.ConvertToVector256Int32
|
||||
public static Vector256<uint> ConvertToVector256Int32(byte[] source)
|
||||
{
|
||||
if (source.Length < 32) // 32 bytes for 8 uints
|
||||
throw new ArgumentException("Source array must be at least 32 bytes long.");
|
||||
|
||||
uint[] uints = new uint[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uints[i] = BitConverter.ToUInt32(source, i * 4);
|
||||
}
|
||||
|
||||
return new Vector256<uint>(uints);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.ShiftLeftLogical
|
||||
public static Vector256<int> ShiftLeftLogical(Vector256<int> metaVec, int shift)
|
||||
{
|
||||
int[] shifted = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
shifted[i] = (int)(metaVec.data[i] << shift);
|
||||
}
|
||||
return new Vector256<int>(shifted);
|
||||
}
|
||||
// 模拟 Avx2.And
|
||||
public static Vector256<int> And(Vector256<int> data1, Vector256<int> data2)
|
||||
{
|
||||
int[] result = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] & data2.data[i];
|
||||
}
|
||||
return new Vector256<int>(result);
|
||||
}
|
||||
// 模拟 Avx2.And
|
||||
public static Vector256<uint> And(Vector256<uint> data1, Vector256<uint> data2)
|
||||
{
|
||||
uint[] result = new uint[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] & data2.data[i];
|
||||
}
|
||||
|
||||
return new Vector256<uint>(result);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.Or
|
||||
public static Vector256<int> Or(Vector256<int> data1, Vector256<int> data2)
|
||||
{
|
||||
int[] result = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] | data2.data[i];
|
||||
}
|
||||
|
||||
return new Vector256<int>(result);
|
||||
}
|
||||
// 模拟 Avx2.Or
|
||||
public static Vector256<uint> Or(Vector256<uint> data1, Vector256<uint> data2)
|
||||
{
|
||||
uint[] result = new uint[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] | data2.data[i];
|
||||
}
|
||||
|
||||
return new Vector256<uint>(result);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.Xor
|
||||
|
||||
public static Vector256<int> Xor(Vector256<int> data1, Vector256<int> data2)
|
||||
{
|
||||
int[] result = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] ^ data2.data[i];
|
||||
}
|
||||
return new Vector256<int>(result);
|
||||
}
|
||||
|
||||
public static Vector256<uint> Xor(Vector256<uint> data1, Vector256<uint> data2)
|
||||
{
|
||||
uint[] result = new uint[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] ^ data2.data[i];
|
||||
}
|
||||
|
||||
return new Vector256<uint>(result);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.Permute2x128 (只模拟上下两半交换)
|
||||
public static Vector256<uint> Permute2x128(Vector256<uint> data)
|
||||
{
|
||||
uint[] swapped = new uint[8];
|
||||
Array.Copy(data.data, 4, swapped, 0, 4);
|
||||
Array.Copy(data.data, 0, swapped, 4, 4);
|
||||
return new Vector256<uint>(swapped);
|
||||
}
|
||||
// 模拟 Avx2.Permute2x128 (只模拟上下两半交换)
|
||||
public static Vector256<int> Permute2x128(Vector256<int> data)
|
||||
{
|
||||
int[] swapped = new int[8];
|
||||
Array.Copy(data.data, 4, swapped, 0, 4);
|
||||
Array.Copy(data.data, 0, swapped, 4, 4);
|
||||
return new Vector256<int>(swapped);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.CompareEqual
|
||||
public static Vector256<int> CompareEqual(Vector256<int> left, Vector256<int> right)
|
||||
{
|
||||
int[] result = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = (left.data[i] == right.data[i]) ? -1 : 0; // 通常使用-1或全1表示真,0表示假
|
||||
}
|
||||
return new Vector256<int>(result);
|
||||
}
|
||||
// 模拟 MaskedStore 操作
|
||||
public static void MaskedStore(Vector256<int> destination, Vector256<int> mask, Vector256<int> source)
|
||||
{
|
||||
//if (destination.Length < 8 || mask.Length != 8 || source.data.Length != 8)
|
||||
// throw new ArgumentException("Arrays must be of length 8 and match in size.");
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
// 如果掩码中的值为非零(表示真),则写入源向量的值
|
||||
if (mask.data[i] != 0)
|
||||
{
|
||||
destination.data[i] = source.data[i];
|
||||
}
|
||||
// 如果掩码中的值为零(表示假),则不修改destination[i]
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:这不是AVX2的gather指令的直接模拟,而是一个简化的示例
|
||||
public static Vector256<int> GatherVector256(T[] source, int[] indices)
|
||||
{
|
||||
if (source.Length < 8 || indices.Length < 8)
|
||||
throw new ArgumentException("Source array and indices array must be large enough.");
|
||||
|
||||
int[] gatheredData = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int index = indices[i];
|
||||
if (index < 0 || index >= source.Length)
|
||||
gatheredData[i] = source[index];
|
||||
}
|
||||
|
||||
return new Vector256<int>(gatheredData);
|
||||
}
|
||||
|
||||
|
||||
// 模拟 ShiftRightLogicalVariable,接受两个 Vector256<uint> 参数
|
||||
// 第一个参数是要右移的向量,第二个参数是每个元素要右移的位数
|
||||
public static Vector256<uint> ShiftRightLogicalVariable(this Vector256<uint> vector, Vector256<uint> shiftCounts)
|
||||
{
|
||||
if (vector.data.Length != shiftCounts.data.Length)
|
||||
{
|
||||
throw new ArgumentException("Both vectors must have the same number of elements.");
|
||||
}
|
||||
|
||||
uint[] shiftedData = new uint[Vector256<uint>.ElementCount];
|
||||
for (int i = 0; i < vector.data.Length; i++)
|
||||
{
|
||||
// 注意:这里假设 shiftCounts.data[i] 中的值是有效的右移位数(非负且小于 32)
|
||||
// 如果需要,可以添加额外的检查来处理无效值
|
||||
int shiftCount = (int)shiftCounts.data[i] & 0x1F; // 限制右移位数在 0 到 31 之间
|
||||
shiftedData[i] = vector.data[i] >> shiftCount;
|
||||
}
|
||||
|
||||
return new Vector256<uint>(shiftedData);
|
||||
}
|
||||
|
||||
}
|
8
Assets/Resources.meta
Normal file
8
Assets/Resources.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f82937a8bdf895f4788174f079d4941d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Assets/Resources/MarioAdvance(J).gba.bytes
Normal file
BIN
Assets/Resources/MarioAdvance(J).gba.bytes
Normal file
Binary file not shown.
7
Assets/Resources/MarioAdvance(J).gba.bytes.meta
Normal file
7
Assets/Resources/MarioAdvance(J).gba.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2ea7bc8e7d2a9d4383c5e586779c6ad
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Assets/Resources/gba_bios.bin.bytes
Normal file
BIN
Assets/Resources/gba_bios.bin.bytes
Normal file
Binary file not shown.
7
Assets/Resources/gba_bios.bin.bytes.meta
Normal file
7
Assets/Resources/gba_bios.bin.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99103839218b11c42820fbe24d848833
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Assets/Resources/mario2.gba.bytes
Normal file
BIN
Assets/Resources/mario2.gba.bytes
Normal file
Binary file not shown.
7
Assets/Resources/mario2.gba.bytes.meta
Normal file
7
Assets/Resources/mario2.gba.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9de3173972725794ab0d4d305c3f88b0
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Assets/Resources/mario_world.gba.bytes
Normal file
BIN
Assets/Resources/mario_world.gba.bytes
Normal file
Binary file not shown.
7
Assets/Resources/mario_world.gba.bytes.meta
Normal file
7
Assets/Resources/mario_world.gba.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 152604cac101e294ab24dbfab7954669
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1 +0,0 @@
|
||||
//[module: System.Runtime.CompilerServices.SkipLocalsInit]
|
80
Assets/emulator/AudioProvider.cs
Normal file
80
Assets/emulator/AudioProvider.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 758bfc6cd0551814abf32fc61d504c02
|
||||
guid: af17dd239b0eea44592e0cd125b2e3b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -126,8 +126,7 @@ namespace OptimeGBA
|
||||
|
||||
if (sample < CurrentSampleOutPos)
|
||||
{
|
||||
Console.Error.WriteLine("Tried to set amplitude backward in time!");
|
||||
Console.WriteLine(System.Environment.StackTrace);
|
||||
//Console.WriteLine("Tried to set amplitude backward in time!");
|
||||
}
|
||||
|
||||
ChannelSample[channel] = sample;
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2f0e6f7505a3d04bb84e704d1ed8d33
|
||||
guid: 89bf553b1fa17dd4fa93a74803b75d0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,770 +0,0 @@
|
||||
using static Util;
|
||||
using static OptimeGBA.Bits;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using System;
|
||||
using System.Text;
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum SpiEepromState
|
||||
{
|
||||
Ready,
|
||||
ReadStatus,
|
||||
WriteStatus,
|
||||
SetReadAddress,
|
||||
SetWriteAddress,
|
||||
ReadData,
|
||||
WriteData,
|
||||
Done,
|
||||
}
|
||||
|
||||
public enum ExternalMemoryType
|
||||
{
|
||||
None,
|
||||
Eeprom,
|
||||
Flash,
|
||||
FlashWithInfrared
|
||||
}
|
||||
|
||||
public enum CartridgeState
|
||||
{
|
||||
Dummy,
|
||||
ReadCartridgeHeader,
|
||||
ReadRomChipId1,
|
||||
Dummy2,
|
||||
ReadRomChipId2,
|
||||
Key2DataRead,
|
||||
SecureAreaRead,
|
||||
ReadRomChipId3,
|
||||
}
|
||||
|
||||
public class CartridgeNds
|
||||
{
|
||||
Nds Nds;
|
||||
byte[] Rom;
|
||||
byte[] SecureArea = new byte[0x4000];
|
||||
|
||||
public uint[] EncLutKeycodeLevel1 = new uint[0x412];
|
||||
public uint[] EncLutKeycodeLevel2 = new uint[0x412];
|
||||
public uint[] EncLutKeycodeLevel3 = new uint[0x412];
|
||||
|
||||
public uint IdCode;
|
||||
public string IdString;
|
||||
|
||||
public CartridgeNds(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
Rom = Nds.Provider.Rom;
|
||||
|
||||
for (uint i = 0; i < 0x412; i++)
|
||||
{
|
||||
uint val = GetUint(Nds.Provider.Bios7, 0x30 + i * 4);
|
||||
EncLutKeycodeLevel1[i] = val;
|
||||
EncLutKeycodeLevel2[i] = val;
|
||||
EncLutKeycodeLevel3[i] = val;
|
||||
}
|
||||
|
||||
if (Rom.Length >= 0x10)
|
||||
{
|
||||
IdCode = GetUint(Rom, 0x0C);
|
||||
|
||||
Span<byte> gameIdSpan = stackalloc byte[4];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
gameIdSpan[i] = GetByte(Rom, 0x0C + (uint)i);
|
||||
}
|
||||
|
||||
IdString = Encoding.ASCII.GetString(gameIdSpan);
|
||||
Console.WriteLine("Game ID: " + IdString);
|
||||
}
|
||||
|
||||
InitKeycode(EncLutKeycodeLevel1, 1);
|
||||
InitKeycode(EncLutKeycodeLevel2, 2);
|
||||
InitKeycode(EncLutKeycodeLevel3, 3);
|
||||
|
||||
if (!Nds.Provider.DirectBoot && Rom.Length >= 0x8000 && GetUint(Rom, 0x4000) == 0xE7FFDEFF)
|
||||
{
|
||||
for (uint i = 0; i < 0x4000; i++)
|
||||
{
|
||||
SecureArea[i] = Rom[0x4000 + i];
|
||||
}
|
||||
Console.WriteLine("Encrypting first 2KB of secure area");
|
||||
SetUlong(SecureArea, 0x0000, 0x6A624F7972636E65); // Write in "encryObj"
|
||||
|
||||
// Encrypt first 2K of the secure area with KEY1
|
||||
for (uint i = 0x0000; i < 0x0800; i += 8)
|
||||
{
|
||||
// Console.WriteLine("Encrypted ulong at " + Hex(i, 16));
|
||||
ulong raw = GetUlong(SecureArea, i);
|
||||
ulong encrypted = Encrypt64(EncLutKeycodeLevel3, raw);
|
||||
SetUlong(SecureArea, i, encrypted);
|
||||
// Console.WriteLine("Before:" + Hex(raw, 16));
|
||||
// Console.WriteLine("After :" + Hex(encrypted, 16));
|
||||
}
|
||||
|
||||
Console.WriteLine(Hex(GetUint(SecureArea, 0x0010), 8));
|
||||
|
||||
// Double-encrypt KEY1
|
||||
SetUlong(SecureArea, 0x0000, Encrypt64(EncLutKeycodeLevel2, GetUlong(SecureArea, 0x0000)));
|
||||
}
|
||||
|
||||
for (uint i = 0; i < ExternalMemory.Length; i++)
|
||||
{
|
||||
ExternalMemory[i] = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
ulong PendingCommand;
|
||||
|
||||
// some GBATek example
|
||||
// TODO: Replace this with something more realistic, maybe from a game DB
|
||||
public uint RomChipId = 0x00001FC2;
|
||||
|
||||
// State
|
||||
public CartridgeState State;
|
||||
public uint DataPos;
|
||||
public uint BytesTransferred;
|
||||
public bool Key1Encryption;
|
||||
public bool Key2Encryption;
|
||||
|
||||
public uint TransferLength;
|
||||
public uint PendingDummyWrites;
|
||||
|
||||
public bool ReadyBit23;
|
||||
public byte BlockSize;
|
||||
public bool SlowTransferClock;
|
||||
public bool BusyBit31;
|
||||
|
||||
// AUXSPICNT
|
||||
public byte SpiBaudRate;
|
||||
public bool SpiChipSelHold = false;
|
||||
public bool SpiBusy = false;
|
||||
public bool Slot1SpiMode = false;
|
||||
public bool TransferReadyIrq = false;
|
||||
public bool Slot1Enable = false;
|
||||
|
||||
// Shared External Memory State
|
||||
public SpiEepromState SpiEepromState;
|
||||
public byte SpiOutData;
|
||||
public uint SpiAddress;
|
||||
public uint SpiBytesWritten;
|
||||
public bool ExternalMemoryWriteEnable;
|
||||
// only one game called art academy uses more than 1MB
|
||||
public byte[] ExternalMemory = new byte[1048576];
|
||||
|
||||
// EEPROM state
|
||||
public byte EepromWriteProtect;
|
||||
|
||||
// Flash state
|
||||
// From Nocash's original DS
|
||||
// TODO: use more realistic flash ID
|
||||
byte[] FlashId = new byte[] { 0x20, 0x40, 0x12 };
|
||||
public SpiFlashState SpiFlashState;
|
||||
public byte FlashIdIndex;
|
||||
|
||||
// ROMCTRL
|
||||
byte ROMCTRLB0;
|
||||
byte ROMCTRLB1;
|
||||
bool ReleaseReset;
|
||||
|
||||
// cart input
|
||||
uint InData;
|
||||
|
||||
public byte ReadHwio8(bool fromArm7, uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
if (fromArm7 == Nds.MemoryControl.Nds7Slot1AccessRights)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x40001A0: // AUXSPICNT B0
|
||||
val |= SpiBaudRate;
|
||||
if (SpiChipSelHold) val = BitSet(val, 6);
|
||||
if (SpiBusy) val = BitSet(val, 7);
|
||||
// Console.WriteLine("AUXSPICNT B0 read");
|
||||
break;
|
||||
case 0x40001A1: // AUXSPICNT B1
|
||||
if (Slot1SpiMode) val = BitSet(val, 5);
|
||||
if (TransferReadyIrq) val = BitSet(val, 6);
|
||||
if (Slot1Enable) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x40001A2: // AUXSPIDATA
|
||||
return SpiOutData;
|
||||
|
||||
case 0x40001A4: // ROMCTRL B0
|
||||
return ROMCTRLB0;
|
||||
case 0x40001A5: // ROMCTRL B1
|
||||
return ROMCTRLB1;
|
||||
case 0x40001A6: // ROMCTRL B2
|
||||
if (ReleaseReset) val = BitSet(val, 5);
|
||||
if (ReadyBit23) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x40001A7: // ROMCTRL B3
|
||||
val |= BlockSize;
|
||||
if (SlowTransferClock) val = BitSet(val, 3);
|
||||
if (BusyBit31) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4100010: // From cartridge
|
||||
if (Slot1Enable)
|
||||
{
|
||||
ReadData(fromArm7);
|
||||
}
|
||||
return (byte)(InData >> 0);
|
||||
case 0x4100011:
|
||||
return (byte)(InData >> 8);
|
||||
case 0x4100012:
|
||||
return (byte)(InData >> 16);
|
||||
case 0x4100013:
|
||||
return (byte)(InData >> 24);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine((fromArm7 ? "ARM7" : "ARM9") + " tried to read from Slot 1 @ " + Hex(addr, 8));
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(bool fromArm7, uint addr, byte val)
|
||||
{
|
||||
if (fromArm7 == Nds.MemoryControl.Nds7Slot1AccessRights)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x40001A0: // AUXSPICNT B0
|
||||
SpiBaudRate = (byte)(val & 0b11);
|
||||
SpiChipSelHold = BitTest(val, 6);
|
||||
SpiBusy = BitTest(val, 7);
|
||||
return;
|
||||
case 0x40001A1: // AUXSPICNT B1
|
||||
Slot1SpiMode = BitTest(val, 5);
|
||||
TransferReadyIrq = BitTest(val, 6);
|
||||
Slot1Enable = BitTest(val, 7);
|
||||
return;
|
||||
|
||||
case 0x40001A2: // AUXSPIDATA
|
||||
SpiTransferTo(val);
|
||||
break;
|
||||
|
||||
case 0x40001A4: // ROMCTRL B0
|
||||
ROMCTRLB0 = val;
|
||||
break;
|
||||
case 0x40001A5: // ROMCTRL B1
|
||||
ROMCTRLB1 = val;
|
||||
break;
|
||||
case 0x40001A6: // ROMCTRL B2
|
||||
if (BitTest(val, 5)) ReleaseReset = true;
|
||||
break;
|
||||
case 0x40001A7: // ROMCTRL B3
|
||||
BlockSize = (byte)(val & 0b111);
|
||||
SlowTransferClock = BitTest(val, 3);
|
||||
|
||||
if (BitTest(val, 7) && !BusyBit31 && Slot1Enable)
|
||||
{
|
||||
ProcessCommand(fromArm7);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Slot1Enable)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x40001A8: // Slot 1 Command out
|
||||
case 0x40001A9:
|
||||
case 0x40001AA:
|
||||
case 0x40001AB:
|
||||
case 0x40001AC:
|
||||
case 0x40001AD:
|
||||
case 0x40001AE:
|
||||
case 0x40001AF:
|
||||
if (Slot1Enable)
|
||||
{
|
||||
int shiftBy = (int)((7 - (addr & 7)) * 8);
|
||||
PendingCommand &= (ulong)(~(0xFFUL << shiftBy));
|
||||
PendingCommand |= (ulong)val << shiftBy;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine((fromArm7 ? "ARM7" : "ARM9") + " tried to read from Slot 1 @ " + Hex(addr, 8));
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessCommand(bool fromArm7)
|
||||
{
|
||||
ulong cmd = PendingCommand;
|
||||
if (Key1Encryption)
|
||||
{
|
||||
cmd = Decrypt64(EncLutKeycodeLevel2, cmd);
|
||||
}
|
||||
|
||||
// Console.WriteLine("Slot 1 CMD: " + Hex(cmd, 16));
|
||||
|
||||
if (BlockSize == 0)
|
||||
{
|
||||
TransferLength = 0;
|
||||
}
|
||||
else if (BlockSize == 7)
|
||||
{
|
||||
TransferLength = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
TransferLength = 0x100U << BlockSize;
|
||||
}
|
||||
|
||||
if (TransferLength != 0)
|
||||
{
|
||||
DataPos = 0;
|
||||
BytesTransferred = 0;
|
||||
}
|
||||
|
||||
BusyBit31 = true;
|
||||
|
||||
if (cmd == 0x9F00000000000000)
|
||||
{
|
||||
State = CartridgeState.Dummy;
|
||||
}
|
||||
else if (cmd == 0x0000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Putting up cartridge header");
|
||||
State = CartridgeState.ReadCartridgeHeader;
|
||||
}
|
||||
else if (cmd == 0x9000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Putting up ROM chip ID 1");
|
||||
State = CartridgeState.ReadRomChipId1;
|
||||
}
|
||||
else if ((cmd & 0xFF00000000000000) == 0x3C00000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Enabled KEY1 encryption");
|
||||
State = CartridgeState.Dummy2;
|
||||
Key1Encryption = true;
|
||||
}
|
||||
else if ((cmd & 0xF000000000000000) == 0x2000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Get Secure Area Block");
|
||||
State = CartridgeState.SecureAreaRead;
|
||||
DataPos = (uint)(((cmd >> 44) & 0xFFFF) * 0x1000);
|
||||
// Console.WriteLine("Secure area read pos: " + Hex(DataPos, 8));
|
||||
}
|
||||
else if ((cmd & 0xF000000000000000) == 0x4000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Enable KEY2");
|
||||
State = CartridgeState.Dummy2;
|
||||
}
|
||||
else if ((cmd & 0xF000000000000000) == 0x1000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Putting up ROM chip ID 2");
|
||||
State = CartridgeState.ReadRomChipId2;
|
||||
}
|
||||
else if ((cmd & 0xF000000000000000) == 0xA000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Enter main data mode");
|
||||
State = CartridgeState.Dummy2;
|
||||
Key1Encryption = false;
|
||||
}
|
||||
else if ((cmd & 0xFF00000000FFFFFF) == 0xB700000000000000)
|
||||
{
|
||||
// On a real DS, KEY2 encryption is transparent to software,
|
||||
// as it is all handled in the hardware cartridge interface.
|
||||
// Plus, DS ROM dumps are usually KEY2 decrypted, so in most cases
|
||||
// there's actually no need to actually handle KEY2 encryption in
|
||||
// an emulator.
|
||||
// Console.WriteLine("KEY2 data read");
|
||||
State = CartridgeState.Key2DataRead;
|
||||
|
||||
DataPos = (uint)((cmd >> 24) & 0xFFFFFFFF);
|
||||
// Console.WriteLine("Addr: " + Hex(DataPos, 8));
|
||||
}
|
||||
else if (cmd == 0xB800000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Putting up ROM chip ID 3");
|
||||
State = CartridgeState.ReadRomChipId3;
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new NotImplementedException("Slot 1: unimplemented command " + Hex(cmd, 16));
|
||||
}
|
||||
// If block size is zero, no transfer will take place, signal end.
|
||||
if (TransferLength == 0)
|
||||
{
|
||||
FinishTransfer();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadyBit23 = true;
|
||||
|
||||
// Trigger Slot 1 DMA
|
||||
Nds.Scheduler.AddEventRelative(SchedulerId.None, 0, RepeatCartridgeTransfer);
|
||||
// Console.WriteLine("Trigger slot 1 DMA, Dest: " + Hex(Nds.Dma7.Ch[3].DmaDest, 8));
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadData(bool fromArm7)
|
||||
{
|
||||
if (!ReadyBit23)
|
||||
{
|
||||
InData = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
uint val = 0xFFFFFFFF;
|
||||
|
||||
switch (State)
|
||||
{
|
||||
case CartridgeState.Dummy: // returns all 1s
|
||||
break;
|
||||
case CartridgeState.ReadCartridgeHeader:
|
||||
val = GetUint(Rom, DataPos & 0xFFF);
|
||||
break;
|
||||
case CartridgeState.ReadRomChipId1:
|
||||
case CartridgeState.ReadRomChipId2:
|
||||
case CartridgeState.ReadRomChipId3:
|
||||
val = RomChipId;
|
||||
break;
|
||||
case CartridgeState.Key2DataRead:
|
||||
// Console.WriteLine("Key2 data read");
|
||||
if (DataPos < Rom.Length)
|
||||
{
|
||||
if (DataPos < 0x8000)
|
||||
{
|
||||
DataPos = 0x8000 + (DataPos & 0x1FF);
|
||||
}
|
||||
val = GetUint(Rom, DataPos);
|
||||
}
|
||||
break;
|
||||
case CartridgeState.SecureAreaRead:
|
||||
val = GetUint(SecureArea, DataPos - 0x4000);
|
||||
// Console.WriteLine("Secure area read: Pos: " + Hex(DataPos, 8) + " Val: " + Hex(val, 4));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Slot 1: bad state");
|
||||
}
|
||||
|
||||
|
||||
DataPos += 4;
|
||||
BytesTransferred += 4;
|
||||
if (BytesTransferred >= TransferLength)
|
||||
{
|
||||
FinishTransfer();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Slot 1 DMA transfers
|
||||
Nds.Scheduler.AddEventRelative(SchedulerId.None, 0, RepeatCartridgeTransfer);
|
||||
}
|
||||
|
||||
InData = val;
|
||||
}
|
||||
|
||||
public void RepeatCartridgeTransfer(long cyclesLate)
|
||||
{
|
||||
// Console.WriteLine(Hex(Nds.Dma7.Ch[3].DmaDest, 8));
|
||||
if (Nds.MemoryControl.Nds7Slot1AccessRights)
|
||||
{
|
||||
Nds.Dma7.Repeat((byte)DmaStartTimingNds7.Slot1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Nds.Dma9.Repeat((byte)DmaStartTimingNds9.Slot1);
|
||||
}
|
||||
}
|
||||
|
||||
public void FinishTransfer()
|
||||
{
|
||||
ReadyBit23 = false;
|
||||
BusyBit31 = false;
|
||||
|
||||
if (TransferReadyIrq)
|
||||
{
|
||||
if (Nds.MemoryControl.Nds7Slot1AccessRights)
|
||||
{
|
||||
Nds.HwControl7.FlagInterrupt((uint)InterruptNds.Slot1DataTransferComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
Nds.HwControl9.FlagInterrupt((uint)InterruptNds.Slot1DataTransferComplete);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From the Key1 Encryption section of GBATek.
|
||||
// Thanks Martin Korth.
|
||||
public static ulong Encrypt64(uint[] encLut, ulong val)
|
||||
{
|
||||
uint y = (uint)val;
|
||||
uint x = (uint)(val >> 32);
|
||||
for (uint i = 0; i < 0x10; i++)
|
||||
{
|
||||
uint z = encLut[i] ^ x;
|
||||
x = encLut[0x012 + (byte)(z >> 24)];
|
||||
x = encLut[0x112 + (byte)(z >> 16)] + x;
|
||||
x = encLut[0x212 + (byte)(z >> 8)] ^ x;
|
||||
x = encLut[0x312 + (byte)(z >> 0)] + x;
|
||||
x ^= y;
|
||||
y = z;
|
||||
}
|
||||
uint outLower = x ^ encLut[0x10];
|
||||
uint outUpper = y ^ encLut[0x11];
|
||||
|
||||
return ((ulong)outUpper << 32) | outLower;
|
||||
}
|
||||
|
||||
public static ulong Decrypt64(uint[] encLut, ulong val)
|
||||
{
|
||||
uint y = (uint)val;
|
||||
uint x = (uint)(val >> 32);
|
||||
for (uint i = 0x11; i >= 0x02; i--)
|
||||
{
|
||||
uint z = encLut[i] ^ x;
|
||||
x = encLut[0x012 + (byte)(z >> 24)];
|
||||
x = encLut[0x112 + (byte)(z >> 16)] + x;
|
||||
x = encLut[0x212 + (byte)(z >> 8)] ^ x;
|
||||
x = encLut[0x312 + (byte)(z >> 0)] + x;
|
||||
x ^= y;
|
||||
y = z;
|
||||
}
|
||||
uint outLower = x ^ encLut[0x1];
|
||||
uint outUpper = y ^ encLut[0x0];
|
||||
|
||||
return ((ulong)outUpper << 32) | outLower;
|
||||
}
|
||||
|
||||
// modulo is always 0x08
|
||||
public void ApplyKeycode(uint[] encLut, Span<uint> keyCode, uint modulo)
|
||||
{
|
||||
ulong encrypted1 = Encrypt64(encLut, ((ulong)keyCode[2] << 32) | keyCode[1]);
|
||||
keyCode[1] = (uint)encrypted1;
|
||||
keyCode[2] = (uint)(encrypted1 >> 32);
|
||||
ulong encrypted0 = Encrypt64(encLut, ((ulong)keyCode[1] << 32) | keyCode[0]);
|
||||
keyCode[0] = (uint)encrypted0;
|
||||
keyCode[1] = (uint)(encrypted0 >> 32);
|
||||
|
||||
ulong scratch = 0;
|
||||
|
||||
for (uint i = 0; i < 0x12; i++)
|
||||
{
|
||||
encLut[i] ^= BSwap32(keyCode[(int)(i % modulo)]);
|
||||
}
|
||||
|
||||
// EncLut is stored in uint for convenience so iterate in uints as well
|
||||
for (uint i = 0; i < 0x412; i += 2)
|
||||
{
|
||||
scratch = Encrypt64(encLut, scratch);
|
||||
encLut[i + 0] = (uint)(scratch >> 32);
|
||||
encLut[i + 1] = (uint)scratch;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitKeycode(uint[] encLut, uint level)
|
||||
{
|
||||
Span<uint> keyCode = stackalloc uint[3];
|
||||
keyCode[0] = IdCode;
|
||||
keyCode[1] = IdCode / 2;
|
||||
keyCode[2] = IdCode * 2;
|
||||
|
||||
// For game cartridge KEY1 decryption, modulo is always 2 (says 8 in GBATek)
|
||||
// but is 2 when divided by four to convert from byte to uint
|
||||
if (level >= 1) ApplyKeycode(encLut, keyCode, 2);
|
||||
if (level >= 2) ApplyKeycode(encLut, keyCode, 2);
|
||||
|
||||
keyCode[1] *= 2;
|
||||
keyCode[2] /= 2;
|
||||
|
||||
if (level >= 3) ApplyKeycode(encLut, keyCode, 2); //
|
||||
}
|
||||
|
||||
public static uint BSwap32(uint val)
|
||||
{
|
||||
return
|
||||
((val >> 24) & 0x000000FF) |
|
||||
((val >> 8) & 0x0000FF00) |
|
||||
((val << 8) & 0x00FF0000) |
|
||||
((val << 24) & 0xFF000000);
|
||||
}
|
||||
|
||||
public void SpiTransferTo(byte val)
|
||||
{
|
||||
// currently only EEPROM support
|
||||
if (Slot1Enable)
|
||||
{
|
||||
var saveType = ExternalMemoryType.Eeprom;
|
||||
// TODO: use a game DB to get memory type
|
||||
switch (saveType)
|
||||
{
|
||||
case ExternalMemoryType.None:
|
||||
break;
|
||||
case ExternalMemoryType.Eeprom:
|
||||
switch (SpiEepromState)
|
||||
{
|
||||
case SpiEepromState.Ready:
|
||||
switch (val)
|
||||
{
|
||||
case 0x06: // Write Enable
|
||||
ExternalMemoryWriteEnable = true;
|
||||
SpiEepromState = SpiEepromState.Ready;
|
||||
break;
|
||||
case 0x04: // Write Disable
|
||||
ExternalMemoryWriteEnable = false;
|
||||
SpiEepromState = SpiEepromState.Ready;
|
||||
break;
|
||||
case 0x5: // Read Status Register
|
||||
SpiEepromState = SpiEepromState.ReadStatus;
|
||||
break;
|
||||
case 0x1: // Write Status Register
|
||||
SpiEepromState = SpiEepromState.WriteStatus;
|
||||
break;
|
||||
case 0x9F: // Read JEDEC ID (returns 0xFF on EEPROM/FLASH)
|
||||
SpiOutData = 0xFF;
|
||||
break;
|
||||
case 0x3: // Read
|
||||
SpiEepromState = SpiEepromState.SetReadAddress;
|
||||
SpiAddress = 0;
|
||||
SpiBytesWritten = 0;
|
||||
break;
|
||||
case 0x2: // Write
|
||||
SpiEepromState = SpiEepromState.SetWriteAddress;
|
||||
SpiAddress = 0;
|
||||
SpiBytesWritten = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SpiEepromState.ReadStatus:
|
||||
byte status = 0;
|
||||
if (ExternalMemoryWriteEnable) status = BitSet(status, 1);
|
||||
status |= (byte)(EepromWriteProtect << 2);
|
||||
SpiOutData = status;
|
||||
break;
|
||||
case SpiEepromState.WriteStatus:
|
||||
ExternalMemoryWriteEnable = BitTest(val, 1);
|
||||
EepromWriteProtect = (byte)((val >> 2) & 0b11);
|
||||
break;
|
||||
case SpiEepromState.SetReadAddress:
|
||||
SpiAddress <<= 8;
|
||||
SpiAddress |= val;
|
||||
if (++SpiBytesWritten == 2)
|
||||
{
|
||||
SpiEepromState = SpiEepromState.ReadData;
|
||||
}
|
||||
break;
|
||||
case SpiEepromState.ReadData:
|
||||
SpiOutData = ExternalMemory[SpiAddress];
|
||||
SpiAddress++;
|
||||
break;
|
||||
case SpiEepromState.SetWriteAddress:
|
||||
SpiAddress <<= 8;
|
||||
SpiAddress |= val;
|
||||
if (++SpiBytesWritten == 2)
|
||||
{
|
||||
SpiEepromState = SpiEepromState.WriteData;
|
||||
}
|
||||
break;
|
||||
case SpiEepromState.WriteData:
|
||||
ExternalMemory[SpiAddress] = val;
|
||||
SpiOutData = 0;
|
||||
SpiAddress++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ExternalMemoryType.FlashWithInfrared:
|
||||
switch (SpiFlashState)
|
||||
{
|
||||
case SpiFlashState.TakePrefix:
|
||||
if (val == 0)
|
||||
{
|
||||
SpiFlashState = SpiFlashState.Ready;
|
||||
Console.WriteLine("Flash with IR command");
|
||||
}
|
||||
break;
|
||||
case SpiFlashState.Ready:
|
||||
// Console.WriteLine("SPI: Receive command! " + Hex(val, 2));
|
||||
SpiOutData = 0x00;
|
||||
switch (val)
|
||||
{
|
||||
case 0x06:
|
||||
ExternalMemoryWriteEnable = true;
|
||||
break;
|
||||
case 0x04:
|
||||
ExternalMemoryWriteEnable = false;
|
||||
break;
|
||||
case 0x9F:
|
||||
SpiFlashState = SpiFlashState.Identification;
|
||||
SpiAddress = 0;
|
||||
break;
|
||||
case 0x03:
|
||||
SpiFlashState = SpiFlashState.ReceiveAddress;
|
||||
SpiAddress = 0;
|
||||
SpiBytesWritten = 0;
|
||||
break;
|
||||
case 0x0B:
|
||||
throw new NotImplementedException("slot1 flash fast read");
|
||||
case 0x0A:
|
||||
throw new NotImplementedException("slot1 flash write");
|
||||
case 0x02:
|
||||
throw new NotImplementedException("slot1 flash program");
|
||||
case 0x05: // Identification
|
||||
// Console.WriteLine("SPI ID");
|
||||
SpiAddress = 0;
|
||||
SpiOutData = 0x00;
|
||||
break;
|
||||
case 0x00:
|
||||
break;
|
||||
// default:
|
||||
// throw new NotImplementedException("SPI: Unimplemented command: " + Hex(val, 2));
|
||||
}
|
||||
break;
|
||||
case SpiFlashState.ReceiveAddress:
|
||||
// Console.WriteLine("SPI: Address byte write: " + Hex(val, 2));
|
||||
SpiAddress <<= 8;
|
||||
SpiAddress |= val;
|
||||
if (++SpiBytesWritten == 3)
|
||||
{
|
||||
SpiBytesWritten = 0;
|
||||
SpiFlashState = SpiFlashState.Reading;
|
||||
// Console.WriteLine("SPI: Address written: " + Hex(Address, 6));
|
||||
}
|
||||
break;
|
||||
case SpiFlashState.Reading:
|
||||
// Console.WriteLine("SPI: Read from address: " + Hex(Address, 6));
|
||||
// Nds7.Cpu.Error("SPI");
|
||||
SpiOutData = ExternalMemory[SpiAddress];
|
||||
SpiAddress++;
|
||||
SpiAddress &= 0xFFFFFF;
|
||||
break;
|
||||
case SpiFlashState.Identification:
|
||||
SpiOutData = FlashId[SpiAddress];
|
||||
SpiAddress++;
|
||||
SpiAddress %= 3;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!SpiChipSelHold)
|
||||
{
|
||||
SpiEepromState = SpiEepromState.Ready;
|
||||
SpiFlashState = SpiFlashState.TakePrefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetSave()
|
||||
{
|
||||
return ExternalMemory;
|
||||
}
|
||||
|
||||
public void LoadSave(byte[] sav)
|
||||
{
|
||||
sav.CopyTo(ExternalMemory, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
sealed class Bits
|
||||
public sealed class Bits
|
||||
{
|
||||
public const uint BIT_0 = (1 << 0);
|
||||
public const uint BIT_1 = (1 << 1);
|
||||
@ -121,6 +122,11 @@ namespace OptimeGBA
|
||||
{
|
||||
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)
|
||||
@ -203,5 +209,46 @@ namespace OptimeGBA
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc5bdea9f2094294da4dcefeebda8f63
|
||||
guid: c452c8124018a2748ba32716b989a1ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -5,12 +5,12 @@ namespace OptimeGBA
|
||||
{
|
||||
public class Cp15
|
||||
{
|
||||
Nds Nds;
|
||||
/*Nds Nds;
|
||||
|
||||
public Cp15(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
}
|
||||
}*/
|
||||
|
||||
public uint ControlRegister;
|
||||
|
||||
@ -27,25 +27,25 @@ namespace OptimeGBA
|
||||
ControlRegister = rdVal;
|
||||
ControlRegister |= 0b00000000000000000000000001111000;
|
||||
ControlRegister &= 0b00000000000011111111000010000101;
|
||||
Nds.Mem9.UpdateTcmSettings();
|
||||
//Nds.Mem9.UpdateTcmSettings();
|
||||
break;
|
||||
|
||||
case 0x704:
|
||||
case 0x782:
|
||||
Nds.Cpu9.Halted = true;
|
||||
//Nds.Cpu9.Halted = true;
|
||||
break;
|
||||
|
||||
case 0x910:
|
||||
DataTcmSettings = rdVal;
|
||||
Nds.Mem9.UpdateTcmSettings();
|
||||
//Nds.Mem9.UpdateTcmSettings();
|
||||
break;
|
||||
case 0x911:
|
||||
InstTcmSettings = rdVal;
|
||||
Nds.Mem9.UpdateTcmSettings();
|
||||
//Nds.Mem9.UpdateTcmSettings();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Console.WriteLine($"UNIMPLEMENTED TO CP15 {opcode1},C{cRn},C{cRm},{opcode2}: {HexN(rdVal, 8)}");
|
||||
// Debug.Log($"UNIMPLEMENTED TO CP15 {opcode1},C{cRn},C{cRm},{opcode2}: {HexN(rdVal, 8)}");
|
||||
break;
|
||||
|
||||
}
|
||||
@ -75,7 +75,7 @@ namespace OptimeGBA
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"UNIMPLEMENTED FROM CP15 {opcode1},C{cRn},C{cRm},{opcode2}");
|
||||
//Debug.Log($"UNIMPLEMENTED FROM CP15 {opcode1},C{cRn},C{cRm},{opcode2}");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 930c82bbb70e8d54d8bed296233fb78a
|
||||
guid: 082cf1137d5d5114c8755e473d5857b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d57248ed47e4cf4a922bbb7f923b6e9
|
||||
guid: d1b25dbe9e7a7eb4a8af44963c72a602
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -231,10 +231,10 @@ namespace OptimeGBA
|
||||
if (c.DmaLength == 0) c.DmaLength = 0x4000;
|
||||
}
|
||||
|
||||
// Console.WriteLine($"Starting DMA {ci}");
|
||||
// Console.WriteLine($"SRC: {Util.HexN(srcAddr, 7)}");
|
||||
// Console.WriteLine($"DEST: {Util.HexN(destAddr, 7)}");
|
||||
// Console.WriteLine($"LENGTH: {Util.HexN(c.DmaLength, 4)}");
|
||||
// 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;
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c4ffd474f58e654b873b37f5ca89d16
|
||||
guid: 36717f7dc90280e41826611c592f97a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,417 +0,0 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using static Util;
|
||||
using System;
|
||||
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum DmaStartTimingNds9 : byte
|
||||
{
|
||||
Immediately = 0,
|
||||
VBlank = 1,
|
||||
HBlank = 2,
|
||||
UponRenderBegin = 3,
|
||||
MainMemoryDisplay = 4,
|
||||
Slot1 = 5,
|
||||
Slot2 = 6,
|
||||
GeometryCommandFifo = 7,
|
||||
}
|
||||
|
||||
public enum DmaStartTimingNds7 : byte
|
||||
{
|
||||
Immediately = 0,
|
||||
VBlank = 1,
|
||||
Slot1 = 2,
|
||||
Misc = 3,
|
||||
}
|
||||
|
||||
public sealed class DmaChannelNds
|
||||
{
|
||||
public bool Nds7;
|
||||
|
||||
public DmaChannelNds(bool nds7)
|
||||
{
|
||||
Nds7 = nds7;
|
||||
}
|
||||
|
||||
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 byte 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);
|
||||
if (!Nds7)
|
||||
{
|
||||
StartTiming = (byte)BitRange(DMACNT_H, 11, 13);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartTiming = (byte)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);
|
||||
val |= ((uint)StartTiming & 0b111) << 11;
|
||||
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 DmaNds
|
||||
{
|
||||
bool Nds7;
|
||||
Memory Mem;
|
||||
HwControl HwControl;
|
||||
|
||||
public DmaChannelNds[] Ch;
|
||||
static readonly uint[] DmaSourceMask = { 0x07FFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF };
|
||||
static readonly uint[] DmaDestMask = { 0x07FFFFFF, 0x07FFFFFF, 0x07FFFFFF, 0x0FFFFFFFF };
|
||||
|
||||
public byte[] DmaFill = new byte[16];
|
||||
|
||||
public bool DmaLock;
|
||||
|
||||
public DmaNds(bool nds7, Memory mem, HwControlNds hwControl)
|
||||
{
|
||||
Nds7 = nds7;
|
||||
Mem = mem;
|
||||
HwControl = hwControl;
|
||||
|
||||
Ch = new DmaChannelNds[4] {
|
||||
new DmaChannelNds(Nds7),
|
||||
new DmaChannelNds(Nds7),
|
||||
new DmaChannelNds(Nds7),
|
||||
new DmaChannelNds(Nds7),
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
else if (addr >= 0x40000E0 && addr <= 0x40000EF)
|
||||
{
|
||||
return DmaFill[addr & 0xF];
|
||||
}
|
||||
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;
|
||||
}
|
||||
else if (addr >= 0x40000E0 && addr <= 0x40000EF)
|
||||
{
|
||||
DmaFill[addr & 0xF] = val;
|
||||
return;
|
||||
}
|
||||
throw new Exception("This shouldn't happen.");
|
||||
}
|
||||
|
||||
public void ExecuteDma(DmaChannelNds c, uint ci)
|
||||
{
|
||||
|
||||
DmaLock = true;
|
||||
|
||||
// Console.WriteLine("NDS: Executing DMA");
|
||||
// Console.WriteLine("Source: " + Util.Hex(c.DmaSource, 8));
|
||||
// Console.WriteLine("Dest: " + Util.Hex(c.DmaDest, 8));
|
||||
// Console.WriteLine("Length: " + c.DmaLength);
|
||||
|
||||
if (!Nds7)
|
||||
{
|
||||
c.DmaSource &= 0x0FFFFFFF;
|
||||
c.DmaDest &= 0x0FFFFFFF;
|
||||
|
||||
// All NDS9 DMAs use 21-bit length
|
||||
c.DmaLength &= 0x1FFFFF;
|
||||
// Value of zero is treated as maximum length
|
||||
if (c.DmaLength == 0) c.DmaLength = 0x200000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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 &= 0xFFFF;
|
||||
// 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 &= 0x3FFF;
|
||||
// Value of zero is treated as maximum length
|
||||
if (c.DmaLength == 0) c.DmaLength = 0x4000;
|
||||
}
|
||||
}
|
||||
|
||||
// if (c.DmaLength != 1 && ci == 3)
|
||||
// {
|
||||
// Console.WriteLine(((DmaStartTimingNds7)c.StartTiming).ToString());
|
||||
// Console.WriteLine("DMA length " + c.DmaLength);
|
||||
// }
|
||||
|
||||
// Console.WriteLine($"Starting DMA {ci}");
|
||||
// Console.WriteLine($"SRC: {Util.HexN(srcAddr, 7)}");
|
||||
// Console.WriteLine($"DEST: {Util.HexN(destAddr, 7)}");
|
||||
// Console.WriteLine($"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;
|
||||
|
||||
// TODO: NDS DMA timings
|
||||
if (c.TransferType)
|
||||
{
|
||||
for (; c.DmaLength > 0; c.DmaLength--)
|
||||
{
|
||||
Mem.Write32(c.DmaDest & ~3u, 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--)
|
||||
{
|
||||
Mem.Write16(c.DmaDest & ~1u, Mem.Read16(c.DmaSource & ~1u));
|
||||
// Gba.Tick(Nds.Timing8And16[(c.DmaSource >> 24) & 0xF]);
|
||||
// Gba.Tick(Nds.Timing8And16[(c.DmaDest >> 24) & 0xF]);
|
||||
|
||||
c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest);
|
||||
c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (c.DestAddrCtrl == DmaDestAddrCtrl.IncrementReload)
|
||||
{
|
||||
if (c.Repeat)
|
||||
{
|
||||
c.DmaDest = c.DMADAD;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (c.FinishedIRQ)
|
||||
{
|
||||
HwControl.FlagInterrupt((uint)InterruptNds.Dma0 + ci);
|
||||
}
|
||||
|
||||
DmaLock = false;
|
||||
}
|
||||
|
||||
public void ExecuteImmediate(uint ci)
|
||||
{
|
||||
DmaChannelNds c = Ch[ci];
|
||||
// Console.WriteLine($"NDS{(Nds9 ? "9" : "7")}: Ch{ci} immediate DMA from:{Hex(c.DMASAD, 8)} to:{Hex(c.DMADAD, 8)}");
|
||||
|
||||
if (c.Enabled && c.StartTiming == (byte)DmaStartTimingNds9.Immediately)
|
||||
{
|
||||
c.Disable();
|
||||
|
||||
ExecuteDma(c, ci);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Repeat(byte val)
|
||||
{
|
||||
bool executed = false;
|
||||
if (!DmaLock)
|
||||
{
|
||||
for (uint ci = 0; ci < 4; ci++)
|
||||
{
|
||||
DmaChannelNds c = Ch[ci];
|
||||
if (c.StartTiming == val)
|
||||
{
|
||||
executed = true;
|
||||
c.DmaLength = c.DMACNT_L;
|
||||
ExecuteDma(c, ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
return executed;
|
||||
}
|
||||
}
|
||||
}
|
246
Assets/emulator/Emulator.cs
Normal file
246
Assets/emulator/Emulator.cs
Normal 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!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d82a940594010314bbfb9de2d3865d64
|
||||
guid: 515e71167c74b044984b170a2a141f10
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
38
Assets/emulator/EmulatorGUI.cs
Normal file
38
Assets/emulator/EmulatorGUI.cs
Normal 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
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0aa73f4dabd6dcd44b7d5eaa5cbf4c68
|
||||
guid: 2a6d4732564041e4d85a5fbef37546d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
20
Assets/emulator/GBA.asmdef
Normal file
20
Assets/emulator/GBA.asmdef
Normal 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
|
||||
}
|
7
Assets/emulator/GBA.asmdef.meta
Normal file
7
Assets/emulator/GBA.asmdef.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54cc43fb16a4d83469407e3e8e248065
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -940,11 +940,11 @@ namespace OptimeGBA
|
||||
this.enabled = true;
|
||||
this.frameSequencerStep = 0;
|
||||
|
||||
// Console.WriteLine("Enabled PSGs!");
|
||||
// Debug.Log("Enabled PSGs!");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Console.WriteLine("Disabled PSGs...");
|
||||
// Debug.Log("Disabled PSGs...");
|
||||
|
||||
// Disable and write zeros on everything upon main disabling
|
||||
this.noise_enabled = false;
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05f5a54c4470e584daa86a0c04ddd943
|
||||
guid: ab02a6abe06f10f468911df855dd7604
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -31,8 +31,8 @@ namespace OptimeGBA
|
||||
Ppu = new PpuGba(this, Scheduler);
|
||||
Keypad = new Keypad();
|
||||
Dma = new DmaGba(this);
|
||||
Timers = new Timers(GbaAudio, HwControl, Scheduler, false, true);
|
||||
HwControl = new HwControlGba(this);
|
||||
Timers = new Timers(GbaAudio, HwControl, Scheduler, false, true);
|
||||
Cpu = new Arm7(StateChange, Mem, false, false, null);
|
||||
|
||||
Cpu.SetTimingsTable(
|
||||
@ -132,11 +132,6 @@ namespace OptimeGBA
|
||||
Mem.InitPageTables();
|
||||
Cpu.InitFlushPipeline();
|
||||
|
||||
#if UNSAFE
|
||||
Console.WriteLine("Starting in memory UNSAFE mode");
|
||||
#else
|
||||
Console.WriteLine("Starting in memory SAFE mode");
|
||||
#endif
|
||||
}
|
||||
|
||||
public uint Step()
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c7b25fc293f60540a475ccb2f6357b6
|
||||
guid: 90fdc7ebe54f77c4da8c510515da9f9d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -319,7 +319,7 @@ namespace OptimeGBA
|
||||
const int CyclesPerSample = 16777216 / SampleRate;
|
||||
// public CircularBuffer<short> SampleBuffer = new CircularBuffer<short>(32768, 0);
|
||||
public const uint SampleBufferMax = 256;
|
||||
public short[] SampleBuffer = new short[SampleBufferMax];
|
||||
public float[] SampleBuffer = new float[SampleBufferMax];
|
||||
public uint SampleBufferPos = 0;
|
||||
public bool AudioReady;
|
||||
|
||||
@ -409,8 +409,8 @@ namespace OptimeGBA
|
||||
VisBufB.Insert(CurrentValueB);
|
||||
}
|
||||
|
||||
SampleBuffer[SampleBufferPos++] = (short)((fifoA + psgA) * 64);
|
||||
SampleBuffer[SampleBufferPos++] = (short)((fifoB + psgB) * 64);
|
||||
SampleBuffer[SampleBufferPos++] = ((fifoA + psgA) * 64f)/ SampleRate;
|
||||
SampleBuffer[SampleBufferPos++] = ((fifoB + psgB) * 64f)/ SampleRate;
|
||||
|
||||
if (SampleBufferPos >= SampleBufferMax)
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eda7cec1bdd2dee48802bc4b1456d549
|
||||
guid: 8c5e3b65effa8d24da17efc42e12eb20
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c64a99535514fa649aa2a79581ea1781
|
||||
guid: 816d784dc01e7804ea2368ee2d1f800f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 654dd2b57b6345649802e6a16a013537
|
||||
guid: 8a26fb3a3a7a8044086fc98259d6b58d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,150 +0,0 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum InterruptNds
|
||||
{
|
||||
VBlank = 0,
|
||||
HBlank = 1,
|
||||
VCounterMatch = 2,
|
||||
Timer0Overflow = 3,
|
||||
Timer1Overflow = 4,
|
||||
Timer2Overflow = 5,
|
||||
Timer3Overflow = 6,
|
||||
Rtc = 7,
|
||||
Dma0 = 8,
|
||||
Dma1 = 9,
|
||||
Dma2 = 10,
|
||||
Dma3 = 11,
|
||||
Keypad = 12,
|
||||
GamePak = 13,
|
||||
// 14, 15, unused
|
||||
IpcSync = 16,
|
||||
IpcSendFifoEmpty = 17,
|
||||
IpcRecvFifoPending = 18,
|
||||
Slot1DataTransferComplete = 19,
|
||||
Slot1rq = 20,
|
||||
GeometryFifo = 21, // ARM9 only
|
||||
ScreenUnfold = 22, // ARM7 only
|
||||
SpiBus = 23, // ARM7 only
|
||||
Wifi = 24, // ARM7 only
|
||||
}
|
||||
|
||||
public sealed class HwControlNds : HwControl
|
||||
{
|
||||
Arm7 Cpu;
|
||||
|
||||
public HwControlNds(Arm7 cpu)
|
||||
{
|
||||
Cpu = cpu;
|
||||
}
|
||||
|
||||
public byte Postflg; // POSTFLG
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000208: // IME
|
||||
if (IME) val = BitSet(val, 0);
|
||||
break;
|
||||
|
||||
case 0x4000210: // IE B0
|
||||
return (byte)(IE >> 0);
|
||||
case 0x4000211: // IE B1
|
||||
return (byte)(IE >> 8);
|
||||
case 0x4000212: // IE B2
|
||||
return (byte)(IE >> 16);
|
||||
case 0x4000213: // IE B3
|
||||
return (byte)(IE >> 24);
|
||||
|
||||
case 0x4000214: // IF B0
|
||||
return (byte)(IF >> 0);
|
||||
case 0x4000215: // IF B1
|
||||
return (byte)(IF >> 8);
|
||||
case 0x4000216: // IF B2
|
||||
return (byte)(IF >> 16);
|
||||
case 0x4000217: // IF B3
|
||||
return (byte)(IF >> 24);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000208: // IME
|
||||
IME = BitTest(val, 0);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
|
||||
case 0x4000210: // IE B0
|
||||
IE &= 0xFFFFFF00;
|
||||
IE |= (uint)((uint)val << 0);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000211: // IE B1
|
||||
IE &= 0xFFFF00FF;
|
||||
IE |= (uint)((uint)val << 8);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000212: // IE B2
|
||||
IE &= 0xFF00FFFF;
|
||||
IE |= (uint)((uint)val << 16);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000213: // IE B3
|
||||
IE &= 0x00FFFFFF;
|
||||
IE |= (uint)((uint)val << 24);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
|
||||
case 0x4000214: // IF B0
|
||||
IF &= ~(uint)((uint)val << 0);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000215: // IF B1
|
||||
IF &= ~(uint)((uint)val << 8);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000216: // IF B2
|
||||
IF &= ~(uint)((uint)val << 16);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000217: // IF B3
|
||||
IF &= ~(uint)((uint)val << 24);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlagInterrupt(uint i)
|
||||
{
|
||||
IF |= (uint)(1 << (int)i);
|
||||
CheckAndFireInterrupts();
|
||||
}
|
||||
|
||||
public void CheckAndFireInterrupts()
|
||||
{
|
||||
Available = (IE & IF & 0xFFFFFFFF) != 0;
|
||||
Cpu.FlagInterrupt = Available && IME;
|
||||
if (Cpu.Armv5)
|
||||
{
|
||||
if (Available && IME)
|
||||
{
|
||||
Cpu.Halted = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Available)
|
||||
{
|
||||
Cpu.Halted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 423f62bf81dca7648b5974281bffb372
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,207 +0,0 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed class Ipc
|
||||
{
|
||||
Nds Nds;
|
||||
byte Id;
|
||||
|
||||
public Ipc(Nds nds, byte id)
|
||||
{
|
||||
Nds = nds;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public CircularBuffer<uint> RecvFifo = new CircularBuffer<uint>(16, 0);
|
||||
public uint LastSendValue;
|
||||
public uint LastRecvValue;
|
||||
|
||||
public bool SendFifoEmptyIrqLevel;
|
||||
public bool RecvFifoPendingIrqLevel;
|
||||
|
||||
public byte IpcSyncDataOut;
|
||||
|
||||
// IPCSYNC
|
||||
public bool EnableRemoteIrq;
|
||||
|
||||
// IPCFIFOCNT
|
||||
public bool EnableSendFifoEmptyIrq;
|
||||
public bool EnableRecvFifoPendingIrq;
|
||||
|
||||
public bool FifoError;
|
||||
public bool EnableFifos;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000180: // IPCSYNC B0
|
||||
val |= GetRemote().IpcSyncDataOut;
|
||||
break;
|
||||
case 0x4000181: // IPCSYNC B1
|
||||
val |= IpcSyncDataOut;
|
||||
|
||||
if (EnableRemoteIrq) val = BitSet(val, 14 - 8);
|
||||
break;
|
||||
|
||||
case 0x4000184: // IPCFIFOCNT B0
|
||||
if (GetRemote().RecvFifo.Entries == 0) val = BitSet(val, 0); // Send FIFO empty
|
||||
if (GetRemote().RecvFifo.Entries == 16) val = BitSet(val, 1); // Send FIFO full
|
||||
if (EnableSendFifoEmptyIrq) val = BitSet(val, 2);
|
||||
CheckSendFifoEmptyIrq("IPCFIFOCNT bit enable");
|
||||
break;
|
||||
case 0x4000185: // IPCFIFOCNT B1
|
||||
if (RecvFifo.Entries == 0) val = BitSet(val, 0); // Receive FIFO empty
|
||||
if (RecvFifo.Entries == 16) val = BitSet(val, 1); // Receive FIFO full
|
||||
if (EnableRecvFifoPendingIrq) val = BitSet(val, 2);
|
||||
CheckRecvFifoPendingIrq("IPCFIFOCNT bit enable");
|
||||
|
||||
if (FifoError) val = BitSet(val, 6);
|
||||
if (EnableFifos) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4100000: // IPCFIFORECV B0
|
||||
if (RecvFifo.Entries > 0)
|
||||
{
|
||||
if (EnableFifos)
|
||||
{
|
||||
LastRecvValue = RecvFifo.Pop();
|
||||
GetRemote().CheckSendFifoEmptyIrq("remote pop");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FifoError = true;
|
||||
}
|
||||
val = GetByteIn(LastRecvValue, addr & 3);
|
||||
break;
|
||||
case 0x4100001: // IPCFIFORECV B1
|
||||
case 0x4100002: // IPCFIFORECV B2
|
||||
case 0x4100003: // IPCFIFORECV B3
|
||||
val = GetByteIn(LastRecvValue, addr & 3);
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000180: // IPCSYNC B0
|
||||
break;
|
||||
case 0x4000181: // IPCSYNC B1
|
||||
IpcSyncDataOut = (byte)(val & 0xF);
|
||||
|
||||
// send IRQ to remote
|
||||
if (BitTest(val, 13 - 8) && GetRemote().EnableRemoteIrq)
|
||||
{
|
||||
// Console.WriteLine($"[{Id}] Sending IRQ");
|
||||
switch (Id)
|
||||
{
|
||||
case 0:
|
||||
Nds.HwControl7.FlagInterrupt((uint)InterruptNds.IpcSync);
|
||||
break;
|
||||
case 1:
|
||||
Nds.HwControl9.FlagInterrupt((uint)InterruptNds.IpcSync);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EnableRemoteIrq = BitTest(val, 14 - 8);
|
||||
break;
|
||||
|
||||
case 0x4000184: // IPCFIFOCNT B0
|
||||
EnableSendFifoEmptyIrq = BitTest(val, 2);
|
||||
if (BitTest(val, 3))
|
||||
{
|
||||
GetRemote().RecvFifo.Reset();
|
||||
}
|
||||
break;
|
||||
case 0x4000185: // IPCFIFOCNT B1
|
||||
EnableRecvFifoPendingIrq = BitTest(val, 2);
|
||||
|
||||
if (BitTest(val, 6))
|
||||
{
|
||||
FifoError = false;
|
||||
}
|
||||
EnableFifos = BitTest(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4000188: // IPCFIFOSEND B0
|
||||
case 0x4000189: // IPCFIFOSEND B1
|
||||
case 0x400018A: // IPCFIFOSEND B2
|
||||
LastSendValue = SetByteIn(LastSendValue, val, addr & 3);
|
||||
break;
|
||||
case 0x400018B: // IPCFIFOSEND B3
|
||||
LastSendValue = SetByteIn(LastSendValue, val, addr & 3);
|
||||
if (EnableFifos)
|
||||
{
|
||||
GetRemote().RecvFifo.Insert(LastSendValue);
|
||||
|
||||
bool eligible = true;
|
||||
|
||||
// if ((LastSendValue >> 28) == 0x8) eligible = false;
|
||||
// if ((LastSendValue >> 28) == 0x4) eligible = false;
|
||||
// if ((LastSendValue >> 28) == 0xC) eligible = false;
|
||||
// // if ((LastSendValue >> 28) == 0x0) eligible = false;
|
||||
|
||||
// if (eligible)
|
||||
// {
|
||||
// if (Id == 0) Console.WriteLine("ARM9 to ARM7 " + Util.Hex(LastSendValue, 8));
|
||||
// // else Console.WriteLine("ARM7 to ARM9 " + Util.Hex(LastSendValue, 8));
|
||||
// }
|
||||
|
||||
unsafe
|
||||
{
|
||||
GetRemote().CheckRecvFifoPendingIrq("remote insert R15: " + Util.Hex(Nds.Cpu7.R[15], 8));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Ipc GetRemote()
|
||||
{
|
||||
return Nds.Ipcs[Id ^ 1];
|
||||
}
|
||||
|
||||
public void CheckSendFifoEmptyIrq(string from)
|
||||
{
|
||||
var prev = SendFifoEmptyIrqLevel;
|
||||
SendFifoEmptyIrqLevel = GetRemote().RecvFifo.Entries == 0 && EnableSendFifoEmptyIrq;
|
||||
if (!prev && SendFifoEmptyIrqLevel)
|
||||
{
|
||||
// Console.WriteLine($"Flagging ARM{(Id == 0 ? 7 : 9)} IPC Send FIFO Empty IRQ from " + from);
|
||||
FlagSourceInterrupt(InterruptNds.IpcSendFifoEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckRecvFifoPendingIrq(string from)
|
||||
{
|
||||
var prev = RecvFifoPendingIrqLevel;
|
||||
RecvFifoPendingIrqLevel = RecvFifo.Entries > 0 && EnableRecvFifoPendingIrq;
|
||||
if (!prev && RecvFifoPendingIrqLevel)
|
||||
{
|
||||
// Console.WriteLine($"Flagging ARM{(Id == 0 ? 7 : 9)} IPC Recv FIFO Pending Irq from " + from);
|
||||
FlagSourceInterrupt(InterruptNds.IpcRecvFifoPending);
|
||||
}
|
||||
}
|
||||
|
||||
public void FlagSourceInterrupt(InterruptNds interrupt)
|
||||
{
|
||||
switch (Id)
|
||||
{
|
||||
case 0:
|
||||
Nds.HwControl9.FlagInterrupt((uint)interrupt);
|
||||
break;
|
||||
case 1:
|
||||
Nds.HwControl7.FlagInterrupt((uint)interrupt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84b3353082f9bee4f829c5b77d38c1fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
39
Assets/emulator/KeyMappingButton.cs
Normal file
39
Assets/emulator/KeyMappingButton.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de4ad34ffe4c2a5458453cc7c40a4065
|
||||
guid: 3d8b0ab892d8e9640a62fe93c96abfb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -14,14 +14,6 @@ namespace OptimeGBA
|
||||
public bool Down;
|
||||
public bool R;
|
||||
public bool L;
|
||||
|
||||
// DS Exclusive
|
||||
public bool X;
|
||||
public bool Y;
|
||||
public bool DebugButton;
|
||||
public bool Touch;
|
||||
public bool ScreensOpen = true; // DS folded
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
@ -42,14 +34,6 @@ namespace OptimeGBA
|
||||
if (!L) val = BitSet(val, 9 - 8);
|
||||
break;
|
||||
|
||||
case 0x4000136: // EXTKEYIN - ARM7 only
|
||||
if (!X) val = BitSet(val, 0);
|
||||
if (!Y) val = BitSet(val, 1);
|
||||
if (!DebugButton) val = BitSet(val, 3);
|
||||
if (!Touch) val = BitSet(val, 6);
|
||||
if (!ScreensOpen) val = BitSet(val, 7);
|
||||
// System.Console.WriteLine(Util.Hex(val, 2));
|
||||
break;
|
||||
case 0x4000137: // EXTKEYIN B1
|
||||
val = 0;
|
||||
break;
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46a2eabfa7974ee478fa25cfdc9fa1f1
|
||||
guid: 444bf15c0d0cab040b1b4967a2c3f94c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,8 +1,6 @@
|
||||
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;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3f8f528aeb42c246bc8da7f5e034c36
|
||||
guid: a210b172849cb384e8d6c0a0d4956259
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,157 +0,0 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public class MemoryControlNds
|
||||
{
|
||||
public byte SharedRamControl;
|
||||
|
||||
public byte[] VRAMCNT = new byte[9];
|
||||
public bool VramConfigDirty;
|
||||
|
||||
// EXMEMCNT
|
||||
public byte Slot2SramWaitArm9;
|
||||
public byte Slot2Rom0WaitArm9;
|
||||
public byte Slot2Rom1WaitArm9;
|
||||
public byte Slot2RomPhiPinOutArm9;
|
||||
public byte Slot2SramWaitArm7;
|
||||
public byte Slot2Rom0WaitArm7;
|
||||
public byte Slot2Rom1WaitArm7;
|
||||
public byte Slot2RomPhiPinOutArm7;
|
||||
|
||||
// Shared between 7/9 EXMEMCNT/EXMEMSTAT
|
||||
// true = ARM7
|
||||
public bool Nds7Slot2AccessRights;
|
||||
public bool Nds7Slot1AccessRights;
|
||||
public bool MainMemoryAccessPriority;
|
||||
|
||||
public byte ReadHwio8Nds9(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000204:
|
||||
// Console.WriteLine("read from exmemcnt b0");
|
||||
val |= (byte)((Slot2SramWaitArm9 & 0b11) << 0);
|
||||
val |= (byte)((Slot2Rom0WaitArm9 & 0b11) << 2);
|
||||
val |= (byte)((Slot2Rom1WaitArm9 & 0b1) << 4);
|
||||
val |= (byte)((Slot2RomPhiPinOutArm9 & 0b11) << 5);
|
||||
if (Nds7Slot2AccessRights) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x4000205:
|
||||
// Console.WriteLine("read from exmemcnt b1");
|
||||
if (Nds7Slot1AccessRights) val = BitSet(val, 3);
|
||||
if (MainMemoryAccessPriority) val = BitSet(val, 7);
|
||||
val = BitSet(val, 6);
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Nds9(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000204:
|
||||
// Console.WriteLine("write to exmemcnt b0");
|
||||
Slot2SramWaitArm9 = (byte)BitRange(val, 0, 1);
|
||||
Slot2Rom0WaitArm9 = (byte)BitRange(val, 2, 3);
|
||||
Slot2Rom1WaitArm9 = (byte)BitRange(val, 4, 4);
|
||||
Slot2RomPhiPinOutArm9 = (byte)BitRange(val, 5, 6);
|
||||
Nds7Slot2AccessRights = BitTest(val, 7);
|
||||
break;
|
||||
case 0x4000205:
|
||||
// Console.WriteLine("write to exmemcnt b1");
|
||||
Nds7Slot1AccessRights = BitTest(val, 3);
|
||||
MainMemoryAccessPriority = BitTest(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4000240: if (VRAMCNT[0] != val) VramConfigDirty = true; VRAMCNT[0] = val; break;
|
||||
case 0x4000241: if (VRAMCNT[1] != val) VramConfigDirty = true; VRAMCNT[1] = val; break;
|
||||
case 0x4000242: if (VRAMCNT[2] != val) VramConfigDirty = true; VRAMCNT[2] = val; break;
|
||||
case 0x4000243: if (VRAMCNT[3] != val) VramConfigDirty = true; VRAMCNT[3] = val; break;
|
||||
case 0x4000244: if (VRAMCNT[4] != val) VramConfigDirty = true; VRAMCNT[4] = val; break;
|
||||
case 0x4000245: if (VRAMCNT[5] != val) VramConfigDirty = true; VRAMCNT[5] = val; break;
|
||||
case 0x4000246: if (VRAMCNT[6] != val) VramConfigDirty = true; VRAMCNT[6] = val; break;
|
||||
case 0x4000248: if (VRAMCNT[7] != val) VramConfigDirty = true; VRAMCNT[7] = val; break;
|
||||
case 0x4000249: if (VRAMCNT[8] != val) VramConfigDirty = true; VRAMCNT[8] = val; break;
|
||||
|
||||
case 0x4000247:
|
||||
SharedRamControl = (byte)(val & 0b11);
|
||||
break;
|
||||
}
|
||||
|
||||
// if (VramEnabledAndSet(2, 2) || VramEnabledAndSet(3, 2))
|
||||
// {
|
||||
// throw new NotImplementedException("Implement mapping VRAM banks C and D to ARM7");
|
||||
// }
|
||||
}
|
||||
|
||||
public byte ReadHwio8Nds7(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000204:
|
||||
// Console.WriteLine("read from exmemstat b0");
|
||||
val |= (byte)((Slot2SramWaitArm7 & 0b11) << 0);
|
||||
val |= (byte)((Slot2Rom0WaitArm7 & 0b11) << 2);
|
||||
val |= (byte)((Slot2Rom1WaitArm7 & 0b1) << 4);
|
||||
val |= (byte)((Slot2RomPhiPinOutArm7 & 0b11) << 5);
|
||||
if (Nds7Slot2AccessRights) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x4000205:
|
||||
// Console.WriteLine("read from exmemstat b1");
|
||||
if (Nds7Slot1AccessRights) val = BitSet(val, 3);
|
||||
if (MainMemoryAccessPriority) val = BitSet(val, 7);
|
||||
val = BitSet(val, 6);
|
||||
break;
|
||||
|
||||
case 0x4000240:
|
||||
if (VramEnabledAndSet(2, 2)) val = BitSet(val, 0);
|
||||
if (VramEnabledAndSet(3, 2)) val = BitSet(val, 1);
|
||||
break;
|
||||
case 0x4000241:
|
||||
return SharedRamControl;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Nds7(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000204:
|
||||
// Console.WriteLine("write to exmemstat b0");
|
||||
Slot2SramWaitArm7 = (byte)BitRange(val, 0, 1);
|
||||
Slot2Rom0WaitArm7 = (byte)BitRange(val, 2, 3);
|
||||
Slot2Rom1WaitArm7 = (byte)BitRange(val, 4, 4);
|
||||
Slot2RomPhiPinOutArm7 = (byte)BitRange(val, 5, 6);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool VramEnabledAndSet(uint bank, uint mst)
|
||||
{
|
||||
uint vramcntMst = VRAMCNT[bank] & 0b111U;
|
||||
bool vramcntEnable = BitTest(VRAMCNT[bank], 7);
|
||||
|
||||
return vramcntEnable && vramcntMst == mst;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint GetOffset(uint bank)
|
||||
{
|
||||
return (uint)(VRAMCNT[bank] >> 3) & 0b11U;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cce821ff321fe34ea2e0c5cbabdd74e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -65,7 +65,7 @@ namespace OptimeGBA
|
||||
}
|
||||
breakOuterLoop:
|
||||
|
||||
Console.WriteLine($"Save Type: {strings[matchedIndex]}");
|
||||
//Debug.Log($"Save Type: {strings[matchedIndex]}");
|
||||
|
||||
switch (matchedIndex)
|
||||
{
|
||||
@ -80,7 +80,7 @@ namespace OptimeGBA
|
||||
{
|
||||
EepromThreshold = 0x1FFFF00;
|
||||
}
|
||||
Console.WriteLine("EEPROM Threshold: " + Util.Hex(EepromThreshold, 8));
|
||||
//Debug.Log("EEPROM Threshold: " + Util.Hex(EepromThreshold, 8));
|
||||
break;
|
||||
case 2: SaveProvider = new Sram(); break;
|
||||
case 3: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break;
|
||||
@ -177,7 +177,7 @@ namespace OptimeGBA
|
||||
|
||||
~MemoryGba()
|
||||
{
|
||||
Console.WriteLine("Cleaning up GBA memory...");
|
||||
//Debug.Log("Cleaning up GBA memory...");
|
||||
UnpinByteArray(Bios);
|
||||
UnpinByteArray(Ewram);
|
||||
UnpinByteArray(Iwram);
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3ab94683bca44e4db631e74b158b993
|
||||
guid: 4a6b84548f6fe89488daa4de20cb9164
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,449 +0,0 @@
|
||||
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;
|
||||
using static Util;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed unsafe class MemoryNds7 : Memory
|
||||
{
|
||||
Nds Nds;
|
||||
|
||||
public MemoryNds7(Nds nds, ProviderNds provider)
|
||||
{
|
||||
Nds = nds;
|
||||
|
||||
SaveProvider = new NullSaveProvider();
|
||||
|
||||
for (uint i = 0; i < Arm7BiosSize && i < provider.Bios7.Length; i++)
|
||||
{
|
||||
Arm7Bios[i] = provider.Bios7[i];
|
||||
}
|
||||
}
|
||||
|
||||
public const int Arm7BiosSize = 16384;
|
||||
public const int Arm7WramSize = 65536;
|
||||
|
||||
public byte[] Arm7Bios = new byte[Arm7BiosSize];
|
||||
public byte[] Arm7Wram = new byte[Arm7WramSize];
|
||||
|
||||
public byte RCNT;
|
||||
|
||||
public override void InitPageTable(byte*[] table, uint[] maskTable, bool write)
|
||||
{
|
||||
byte* arm7Bios = TryPinByteArray(Arm7Bios);
|
||||
byte* mainRam = TryPinByteArray(Nds.MainRam);
|
||||
byte* arm7Wram = TryPinByteArray(Arm7Wram);
|
||||
|
||||
// 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] = arm7Bios;
|
||||
}
|
||||
maskTable[i] = 0x00003FFF;
|
||||
break;
|
||||
case 0x2: // Main Memory
|
||||
table[i] = mainRam;
|
||||
maskTable[i] = 0x003FFFFF;
|
||||
break;
|
||||
case 0x3: // Shared RAM / ARM7 WRAM
|
||||
if (addr >= 0x03800000)
|
||||
{
|
||||
table[i] = arm7Wram;
|
||||
maskTable[i] = 0x0000FFFF;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~MemoryNds7()
|
||||
{
|
||||
Console.WriteLine("Cleaning up NDS7 memory...");
|
||||
UnpinByteArray(Arm7Bios);
|
||||
UnpinByteArray(Nds.MainRam);
|
||||
UnpinByteArray(Arm7Wram);
|
||||
}
|
||||
|
||||
public (byte[] array, uint offset) GetSharedRamParams(uint addr)
|
||||
{
|
||||
switch (Nds.MemoryControl.SharedRamControl)
|
||||
{
|
||||
case 0:
|
||||
default:
|
||||
addr &= 0xFFFF; // ARM7 WRAM
|
||||
return (Arm7Wram, addr);
|
||||
case 1:
|
||||
addr &= 0x3FFF; // 1st half of Shared RAM
|
||||
return (Nds.SharedRam, addr);
|
||||
case 2:
|
||||
addr &= 0x3FFF; // 2st half of Shared RAM
|
||||
addr += 0x4000;
|
||||
return (Nds.SharedRam, addr);
|
||||
case 3:
|
||||
addr &= 0x7FFF; // All 32k of Shared RAM
|
||||
return (Nds.SharedRam, addr);
|
||||
}
|
||||
}
|
||||
|
||||
public override byte Read8Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetByte(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
return ReadHwio8(debug, addr);
|
||||
case 0x6: // ARM7 VRAM
|
||||
return Nds.Ppu.ReadVram8Arm7(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override ushort Read16Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetUshort(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
byte f0 = ReadHwio8(debug, addr++);
|
||||
byte f1 = ReadHwio8(debug, addr++);
|
||||
|
||||
ushort u16 = (ushort)((f1 << 8) | (f0 << 0));
|
||||
|
||||
return u16;
|
||||
case 0x6: // VRAM
|
||||
return (ushort)(
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 0) << 0) |
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 1) << 8)
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override uint Read32Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetUint(array, offset);
|
||||
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;
|
||||
case 0x6: // VRAM
|
||||
return (uint)(
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 0) << 0) |
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 1) << 8) |
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 2) << 16) |
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 3) << 24)
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override void Write8Unregistered(bool debug, uint addr, byte val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetByte(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr, val);
|
||||
break;
|
||||
case 0x6: // ARM7 VRAM
|
||||
Nds.Ppu.WriteVram8Arm7(addr, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write16Unregistered(bool debug, uint addr, ushort val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetUshort(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
||||
break;
|
||||
case 0x6: // ARM7 VRAM
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 0, (byte)(val >> 0));
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 1, (byte)(val >> 8));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write32Unregistered(bool debug, uint addr, uint val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetUint(array, offset, val);
|
||||
break;
|
||||
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 0x6: // ARM7 VRAM
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 0, (byte)(val >> 0));
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 1, (byte)(val >> 8));
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 2, (byte)(val >> 16));
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 3, (byte)(val >> 24));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte ReadHwio8(bool debug, uint addr)
|
||||
{
|
||||
if (LogHwioAccesses)
|
||||
{
|
||||
lock (HwioReadLog)
|
||||
{
|
||||
if ((addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioReadLog.TryGetValue(addr, out count);
|
||||
HwioReadLog[addr] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special exceptions for cleanly defined blocks of MMIO
|
||||
if (addr >= 0x4000400 && addr < 0x4000500) // Audio channels
|
||||
{
|
||||
return Nds.Audio.ReadHwio8Channels(addr);
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000004: case 0x4000005: // DISPSTAT
|
||||
case 0x4000006: case 0x4000007: // VCOUNT
|
||||
return Nds.Ppu.ReadHwio8Arm7(addr);
|
||||
|
||||
case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD
|
||||
case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD
|
||||
case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT
|
||||
case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD
|
||||
case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD
|
||||
case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT
|
||||
case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD
|
||||
case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD
|
||||
case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT
|
||||
case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD
|
||||
case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD
|
||||
case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT
|
||||
case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data
|
||||
case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data
|
||||
case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data
|
||||
case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data
|
||||
return Nds.Dma7.ReadHwio8(addr);
|
||||
|
||||
case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0
|
||||
case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1
|
||||
case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2
|
||||
case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3
|
||||
return Nds.Timers7.ReadHwio8(addr);
|
||||
|
||||
case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC
|
||||
case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT
|
||||
case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND
|
||||
case 0x4100000: case 0x4100001: case 0x4100002: case 0x4100003: // IPCFIFORECV
|
||||
return Nds.Ipcs[1].ReadHwio8(addr);
|
||||
|
||||
case 0x40001A0: case 0x40001A1: // AUXSPICNT
|
||||
case 0x40001A2: case 0x40001A3: // AUXSPIDATA
|
||||
case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL
|
||||
case 0x4100010: case 0x4100011: case 0x4100012: case 0x4100013: // Slot 1 Data In
|
||||
return Nds.Cartridge.ReadHwio8(true, addr);
|
||||
|
||||
case 0x40001C0: case 0x40001C1: // SPICNT
|
||||
case 0x40001C2: case 0x40001C3: // SPIDATA
|
||||
return Nds.Spi.ReadHwio8(addr);
|
||||
|
||||
case 0x4000136: case 0x4000137: // EXTKEYIN
|
||||
// Console.WriteLine(Hex(Nds7.Cpu.R[15], 8));
|
||||
goto case 0x4000130;
|
||||
case 0x4000130: case 0x4000131: // KEYINPUT
|
||||
return Nds.Keypad.ReadHwio8(addr);
|
||||
|
||||
case 0x4000204: case 0x4000205: // EXMEMSTAT
|
||||
return Nds.MemoryControl.ReadHwio8Nds7(addr);
|
||||
|
||||
case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME
|
||||
case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE
|
||||
case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF
|
||||
return Nds.HwControl7.ReadHwio8(addr);
|
||||
|
||||
case 0x4000134:
|
||||
return 0x80;
|
||||
case 0x4000135: // Stubbed RCNT
|
||||
return 0;
|
||||
|
||||
case 0x4000138: case 0x4000139: // RTC
|
||||
return Nds.Rtc.ReadHwio8(addr);
|
||||
|
||||
case 0x4000240: case 0x4000241: // Memory Control Status
|
||||
return Nds.MemoryControl.ReadHwio8Nds7(addr);
|
||||
|
||||
case 0x4000500: case 0x4000501: // SOUNDCNT
|
||||
case 0x4000504: case 0x4000505: // SOUNDBIAS
|
||||
case 0x4000508: case 0x4000509: // SNDCAPCNT
|
||||
return Nds.Audio.ReadHwio8(addr);
|
||||
|
||||
case 0x4000300:
|
||||
// Console.WriteLine("NDS7 POSTFLG read");
|
||||
return Nds.HwControl7.Postflg;
|
||||
|
||||
case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1
|
||||
return Nds.ReadHwio8Arm7(addr);
|
||||
}
|
||||
|
||||
// Console.WriteLine($"NDS7: Unmapped MMIO read addr:{Hex(addr, 8)}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void WriteHwio8(bool debug, uint addr, byte val)
|
||||
{
|
||||
if (LogHwioAccesses)
|
||||
{
|
||||
lock (HwioWriteLog)
|
||||
{
|
||||
if ((addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioWriteLog.TryGetValue(addr, out count);
|
||||
HwioWriteLog[addr] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special exceptions for cleanly defined blocks of MMIO
|
||||
if (addr >= 0x4000400 && addr < 0x4000500) // Audio channels
|
||||
{
|
||||
Nds.Audio.WriteHwio8Channels(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000004: case 0x4000005: // DISPSTAT
|
||||
case 0x4000006: case 0x4000007: // VCOUNT
|
||||
Nds.Ppu.WriteHwio8Arm7(addr, val); return;
|
||||
|
||||
case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD
|
||||
case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD
|
||||
case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT
|
||||
case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD
|
||||
case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD
|
||||
case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT
|
||||
case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD
|
||||
case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD
|
||||
case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT
|
||||
case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD
|
||||
case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD
|
||||
case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT
|
||||
case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data
|
||||
case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data
|
||||
case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data
|
||||
case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data
|
||||
Nds.Dma7.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0
|
||||
case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1
|
||||
case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2
|
||||
case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3
|
||||
Nds.Timers7.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC
|
||||
case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT
|
||||
case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND
|
||||
Nds.Ipcs[1].WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x40001A0: case 0x40001A1: // AUXSPICNT
|
||||
case 0x40001A2: case 0x40001A3: // AUXSPIDATA
|
||||
case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL
|
||||
case 0x40001A8: case 0x40001A9: case 0x40001AA: case 0x40001AB: // Slot 1 Command 0-3
|
||||
case 0x40001AC: case 0x40001AD: case 0x40001AE: case 0x40001AF: // Slot 1 Command 4-7
|
||||
Nds.Cartridge.WriteHwio8(true, addr, val); return;
|
||||
|
||||
case 0x40001B0: case 0x40001B1: case 0x40001B2: case 0x40001B3: // Slot 1 KEY2 encryption seed
|
||||
case 0x40001B4: case 0x40001B5: case 0x40001B6: case 0x40001B7:
|
||||
case 0x40001B8: case 0x40001B9: case 0x40001BA: case 0x40001BB:
|
||||
return;
|
||||
|
||||
case 0x40001C0: case 0x40001C1: // SPICNT
|
||||
case 0x40001C2: case 0x40001C3: // SPIDATA
|
||||
Nds.Spi.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000204: case 0x4000205: // EXMEMSTAT
|
||||
Nds.MemoryControl.WriteHwio8Nds7(addr, val); return;
|
||||
|
||||
case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME
|
||||
case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE
|
||||
case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF
|
||||
Nds.HwControl7.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000134: case 0x4000135: // Stubbed RCNT
|
||||
return;
|
||||
|
||||
case 0x4000138: case 0x4000139: // RTC
|
||||
Nds.Rtc.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000500: case 0x4000501: // SOUNDCNT
|
||||
case 0x4000504: case 0x4000505: // SOUNDBIAS
|
||||
case 0x4000508: case 0x4000509: // SNDCAPCNT
|
||||
Nds.Audio.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000300:
|
||||
Console.WriteLine("NDS7 POSTFLG write");
|
||||
Nds.HwControl7.Postflg = (byte)(val & 1);
|
||||
return;
|
||||
|
||||
case 0x4000301:
|
||||
if ((val & 0b11000000) == 0b10000000)
|
||||
{
|
||||
Nds.Cpu7.Halted = true;
|
||||
}
|
||||
return;
|
||||
|
||||
case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1
|
||||
Nds.WriteHwio8Arm7(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
// Console.WriteLine($"NDS7: Unmapped MMIO write addr:{Hex(addr, 8)} val:{Hex(val, 2)}");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3898147a3efcd8499a8a51b3f945ff3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,622 +0,0 @@
|
||||
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;
|
||||
using static Util;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed unsafe class MemoryNds9 : Memory
|
||||
{
|
||||
Nds Nds;
|
||||
|
||||
public MemoryNds9(Nds nds, ProviderNds provider)
|
||||
{
|
||||
Nds = nds;
|
||||
|
||||
SaveProvider = new NullSaveProvider();
|
||||
|
||||
for (uint i = 0; i < Arm9BiosSize && i < provider.Bios9.Length; i++)
|
||||
{
|
||||
Arm9Bios[i] = provider.Bios9[i];
|
||||
}
|
||||
}
|
||||
|
||||
public const int Arm9BiosSize = 4096;
|
||||
public byte[] Arm9Bios = new byte[Arm9BiosSize];
|
||||
public const int ItcmSize = 32768;
|
||||
public byte[] Itcm = new byte[ItcmSize];
|
||||
public const int DtcmSize = 16384;
|
||||
public byte[] Dtcm = new byte[DtcmSize];
|
||||
|
||||
public uint DtcmBase = 0;
|
||||
public uint ItcmVirtualSize = 0;
|
||||
public uint DtcmVirtualSize = 0;
|
||||
public bool ItcmLoadMode = false;
|
||||
public bool DtcmLoadMode = false;
|
||||
|
||||
public override void InitPageTable(byte*[] table, uint[] maskTable, bool write)
|
||||
{
|
||||
byte* mainRam = TryPinByteArray(Nds.MainRam);
|
||||
byte* arm9Bios = TryPinByteArray(Arm9Bios);
|
||||
byte* dtcm = TryPinByteArray(Dtcm);
|
||||
byte* itcm = TryPinByteArray(Itcm);
|
||||
|
||||
// 12 bits shaved off already, shave off another 12 to get 24
|
||||
for (uint i = 0; i < 1048576; i++)
|
||||
{
|
||||
table[i] = null; // Clear everything out first, since on ARM9 things can move around
|
||||
|
||||
uint addr = (uint)(i << 12);
|
||||
switch (i >> 12)
|
||||
{
|
||||
case 0x2: // Main Memory
|
||||
table[i] = mainRam;
|
||||
maskTable[i] = 0x003FFFFF;
|
||||
break;
|
||||
case 0xFF: // BIOS
|
||||
if (!write)
|
||||
{
|
||||
table[i] = arm9Bios;
|
||||
}
|
||||
maskTable[i] = 0x00000FFF;
|
||||
break;
|
||||
}
|
||||
|
||||
if (addr >= DtcmBase && addr < DtcmBase + DtcmVirtualSize)
|
||||
{
|
||||
|
||||
if (write || !DtcmLoadMode)
|
||||
{
|
||||
// Console.WriteLine("DTCM page set at " + Util.Hex(addr, 8));
|
||||
table[i] = dtcm;
|
||||
}
|
||||
maskTable[i] = 0x00003FFF;
|
||||
}
|
||||
|
||||
// ITCM is immovable
|
||||
// ITCM has higher priority so write pages in after DTCM
|
||||
if (addr < ItcmVirtualSize)
|
||||
{
|
||||
if (write || !ItcmLoadMode)
|
||||
{
|
||||
table[i] = itcm;
|
||||
}
|
||||
maskTable[i] = 0x00007FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~MemoryNds9()
|
||||
{
|
||||
Console.WriteLine("Cleaning up NDS9 memory...");
|
||||
UnpinByteArray(Nds.MainRam);
|
||||
UnpinByteArray(Arm9Bios);
|
||||
UnpinByteArray(Dtcm);
|
||||
UnpinByteArray(Itcm);
|
||||
}
|
||||
|
||||
public void UpdateTcmSettings()
|
||||
{
|
||||
// Console.WriteLine("Data TCM Settings: " + Util.Hex(Nds.Cp15.DataTcmSettings, 8));
|
||||
ItcmVirtualSize = 512U << (int)((Nds.Cp15.InstTcmSettings >> 1) & 0x1F);
|
||||
DtcmVirtualSize = 512U << (int)((Nds.Cp15.DataTcmSettings >> 1) & 0x1F);
|
||||
|
||||
DtcmBase = (uint)(Nds.Cp15.DataTcmSettings & 0xFFFFF000);
|
||||
|
||||
ItcmLoadMode = BitTest(Nds.Cp15.ControlRegister, 19);
|
||||
DtcmLoadMode = BitTest(Nds.Cp15.ControlRegister, 17);
|
||||
|
||||
Console.WriteLine("ITCM set to: " + Util.Hex(0, 8) + " - " + Util.Hex(ItcmVirtualSize - 1, 8));
|
||||
Console.WriteLine("DTCM set to: " + Util.Hex(DtcmBase, 8) + " - " + Util.Hex(DtcmBase + DtcmVirtualSize - 1, 8));
|
||||
|
||||
InitPageTables();
|
||||
}
|
||||
|
||||
public (byte[] array, uint offset) GetSharedRamParams(uint addr)
|
||||
{
|
||||
switch (Nds.MemoryControl.SharedRamControl)
|
||||
{
|
||||
case 0:
|
||||
default:
|
||||
addr &= 0x7FFF; // All 32k of Shared RAM
|
||||
return (Nds.SharedRam, addr);
|
||||
case 1:
|
||||
addr &= 0x3FFF; // 2nd half of Shared RAM
|
||||
addr += 0x4000;
|
||||
return (Nds.SharedRam, addr);
|
||||
case 2:
|
||||
addr &= 0x3FFF; // 1st half of Shared RAM
|
||||
return (Nds.SharedRam, addr);
|
||||
case 3:
|
||||
// throw new NotImplementedException("Implement unmapping Shared RAM from ARM9 without EmptyPage, since some game can possibly try to write to the EmptyPage");
|
||||
EmptyPage[0] = 0;
|
||||
return (EmptyPage, 0); // Unmapped
|
||||
}
|
||||
}
|
||||
|
||||
public override byte Read8Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetByte(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
return ReadHwio8(debug, addr);
|
||||
case 0x5: // PPU Palettes
|
||||
return Nds.Ppu.ReadPalettes8(addr);
|
||||
case 0x6: // VRAM
|
||||
return Nds.Ppu.ReadVram8Arm9(addr);
|
||||
case 0x7: // PPU OAM
|
||||
return Nds.Ppu.ReadOam8(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override ushort Read16Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetUshort(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
byte f0 = ReadHwio8(debug, addr++);
|
||||
byte f1 = ReadHwio8(debug, addr++);
|
||||
|
||||
ushort u16 = (ushort)((f1 << 8) | (f0 << 0));
|
||||
|
||||
return u16;
|
||||
case 0x5: // PPU Palettes
|
||||
return Nds.Ppu.ReadPalettes16(addr);
|
||||
case 0x6: // VRAM
|
||||
return (ushort)(
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 0) << 0) |
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 1) << 8)
|
||||
);
|
||||
case 0x7: // PPU OAM
|
||||
return Nds.Ppu.ReadOam16(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override uint Read32Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetUint(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
if (addr >= 0x4000320 && addr < 0x40006A4) // 3D
|
||||
{
|
||||
return Nds.Ppu3D.ReadHwio32(addr);
|
||||
}
|
||||
|
||||
byte f0 = ReadHwio8(debug, addr + 0);
|
||||
byte f1 = ReadHwio8(debug, addr + 1);
|
||||
byte f2 = ReadHwio8(debug, addr + 2);
|
||||
byte f3 = ReadHwio8(debug, addr + 3);
|
||||
|
||||
uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0));
|
||||
|
||||
return u32;
|
||||
case 0x5: // PPU Palettes
|
||||
return Nds.Ppu.ReadPalettes32(addr);
|
||||
case 0x6: // VRAM
|
||||
return (uint)(
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 0) << 0) |
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 1) << 8) |
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 2) << 16) |
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 3) << 24)
|
||||
);
|
||||
case 0x7: // PPU OAM
|
||||
return Nds.Ppu.ReadOam32(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override void Write8Unregistered(bool debug, uint addr, byte val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetByte(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr, val);
|
||||
break;
|
||||
case 0x5: // PPU Palettes - duplicated across upper-lower in 8-bit??
|
||||
Console.WriteLine("NDS: 8-bit write to palettes");
|
||||
// Nds.Ppu.WritePalettes8(addr + 0, val);
|
||||
// Nds.Ppu.WritePalettes8(addr + 1, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write16Unregistered(bool debug, uint addr, ushort val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetUshort(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
||||
break;
|
||||
case 0x5: // PPU Palettes
|
||||
Nds.Ppu.WritePalettes16(addr, val);
|
||||
break;
|
||||
case 0x6: // VRAM
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 0, (byte)(val >> 0));
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 1, (byte)(val >> 8));
|
||||
break;
|
||||
case 0x7: // PPU OAM
|
||||
Nds.Ppu.WriteOam16(addr, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write32Unregistered(bool debug, uint addr, uint val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetUint(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
if (addr >= 0x4000320 && addr < 0x40006A4) // 3D
|
||||
{
|
||||
Nds.Ppu3D.WriteHwio32(addr, val);
|
||||
return;
|
||||
}
|
||||
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
|
||||
Nds.Ppu.WritePalettes32(addr, val);
|
||||
break;
|
||||
case 0x6: // VRAM
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 0, (byte)(val >> 0));
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 1, (byte)(val >> 8));
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 2, (byte)(val >> 16));
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 3, (byte)(val >> 24));
|
||||
break;
|
||||
case 0x7: // PPU OAM
|
||||
Nds.Ppu.WriteOam32(addr, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public byte ReadHwio8(bool debug, uint addr)
|
||||
{
|
||||
if (LogHwioAccesses)
|
||||
{
|
||||
lock (HwioReadLog) {
|
||||
if ((addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioReadLog.TryGetValue(addr, out count);
|
||||
HwioReadLog[addr] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addr >= 0x4000320 && addr < 0x40006A4) // 3D
|
||||
{
|
||||
Console.Error.WriteLine("8-bit or 16-bit read to 3D");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
// Engine A
|
||||
case 0x4000000: case 0x4000001: case 0x4000002: case 0x4000003: // DISPCNT A
|
||||
case 0x4000004: case 0x4000005: // DISPSTAT
|
||||
case 0x4000006: case 0x4000007: // VCOUNT
|
||||
case 0x4000008: case 0x4000009: // BG0CNT
|
||||
case 0x400000A: case 0x400000B: // BG1CNT
|
||||
case 0x400000C: case 0x400000D: // BG2CNT
|
||||
case 0x400000E: case 0x400000F: // BG3CNT
|
||||
case 0x4000010: case 0x4000011: case 0x4000012: case 0x4000013: // BG0OFS
|
||||
case 0x4000014: case 0x4000015: case 0x4000016: case 0x4000017: // BG1OFS
|
||||
case 0x4000018: case 0x4000019: case 0x400001A: case 0x400001B: // BG2OFS
|
||||
case 0x400001C: case 0x400001D: case 0x400001E: case 0x400001F: // BG3OFS
|
||||
case 0x4000020: case 0x4000021: case 0x4000022: case 0x4000023: // BG2PA/PB
|
||||
case 0x4000024: case 0x4000025: case 0x4000026: case 0x4000027: // BG2PC/PD
|
||||
case 0x4000028: case 0x4000029: case 0x400002A: case 0x400002B: // BG2X
|
||||
case 0x400002C: case 0x400002D: case 0x400002E: case 0x400002F: // BG2Y
|
||||
case 0x4000030: case 0x4000031: case 0x4000032: case 0x4000033: // BG3PA/PB
|
||||
case 0x4000034: case 0x4000035: case 0x4000036: case 0x4000037: // BG3PC/PD
|
||||
case 0x4000038: case 0x4000039: case 0x400003A: case 0x400003B: // BG3X
|
||||
case 0x400003C: case 0x400003D: case 0x400003E: case 0x400003F: // BG3Y
|
||||
case 0x4000040: case 0x4000041: case 0x4000042: case 0x4000043: // WINH
|
||||
case 0x4000044: case 0x4000045: case 0x4000046: case 0x4000047: // WINV
|
||||
case 0x4000048: case 0x4000049: case 0x400004A: case 0x400004B: // WININ/OUT
|
||||
case 0x400004C: case 0x400004D: // MOSAIC
|
||||
case 0x4000050: case 0x4000051: // BLDCNT
|
||||
case 0x4000052: case 0x4000053: // BLDALPHA
|
||||
case 0x4000054: case 0x4000055: // BLDY
|
||||
case 0x4000060: case 0x4000061: // DISP3DCNT
|
||||
case 0x4000064: case 0x4000065: case 0x4000066: case 0x4000067: // DISPCAPCNT
|
||||
case 0x400006C: case 0x400006D: // MASTER_BRIGHT
|
||||
|
||||
// Engine B
|
||||
case 0x4001000: case 0x4001001: case 0x4001002: case 0x4001003: // DISPCNT A
|
||||
case 0x4001008: case 0x4001009: // BG0CNT
|
||||
case 0x400100A: case 0x400100B: // BG1CNT
|
||||
case 0x400100C: case 0x400100D: // BG2CNT
|
||||
case 0x400100E: case 0x400100F: // BG3CNT
|
||||
case 0x4001010: case 0x4001011: case 0x4001012: case 0x4001013: // BG0OFS
|
||||
case 0x4001014: case 0x4001015: case 0x4001016: case 0x4001017: // BG1OFS
|
||||
case 0x4001018: case 0x4001019: case 0x400101A: case 0x400101B: // BG2OFS
|
||||
case 0x400101C: case 0x400101D: case 0x400101E: case 0x400101F: // BG3OFS
|
||||
case 0x4001020: case 0x4001021: case 0x4001022: case 0x4001023: // BG2PA/PB
|
||||
case 0x4001024: case 0x4001025: case 0x4001026: case 0x4001027: // BG2PC/PD
|
||||
case 0x4001028: case 0x4001029: case 0x400102A: case 0x400102B: // BG2X
|
||||
case 0x400102C: case 0x400102D: case 0x400102E: case 0x400102F: // BG2Y
|
||||
case 0x4001030: case 0x4001031: case 0x4001032: case 0x4001033: // BG3PA/PB
|
||||
case 0x4001034: case 0x4001035: case 0x4001036: case 0x4001037: // BG3PC/PD
|
||||
case 0x4001038: case 0x4001039: case 0x400103A: case 0x400103B: // BG3X
|
||||
case 0x400103C: case 0x400103D: case 0x400103E: case 0x400103F: // BG3Y
|
||||
case 0x4001040: case 0x4001041: case 0x4001042: case 0x4001043: // WINH
|
||||
case 0x4001044: case 0x4001045: case 0x4001046: case 0x4001047: // WINV
|
||||
case 0x4001048: case 0x4001049: case 0x400104A: case 0x400104B: // WININ/OUT
|
||||
case 0x400104C: case 0x400104D: // MOSAIC
|
||||
case 0x4001050: case 0x4001051: // BLDCNT
|
||||
case 0x4001052: case 0x4001053: // BLDALPHA
|
||||
case 0x4001054: case 0x4001055: // BLDY
|
||||
case 0x400106C: case 0x400106D: // MASTER_BRIGHT
|
||||
return Nds.Ppu.ReadHwio8Arm9(addr);
|
||||
|
||||
case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD
|
||||
case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD
|
||||
case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT
|
||||
case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD
|
||||
case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD
|
||||
case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT
|
||||
case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD
|
||||
case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD
|
||||
case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT
|
||||
case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD
|
||||
case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD
|
||||
case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT
|
||||
case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data
|
||||
case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data
|
||||
case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data
|
||||
case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data
|
||||
return Nds.Dma9.ReadHwio8(addr);
|
||||
|
||||
case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0
|
||||
case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1
|
||||
case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2
|
||||
case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3
|
||||
return Nds.Timers9.ReadHwio8(addr);
|
||||
|
||||
case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC
|
||||
case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT
|
||||
case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND
|
||||
case 0x4100000: case 0x4100001: case 0x4100002: case 0x4100003: // IPCFIFORECV
|
||||
return Nds.Ipcs[0].ReadHwio8(addr);
|
||||
|
||||
case 0x40001A0: case 0x40001A1: // AUXSPICNT
|
||||
case 0x40001A2: case 0x40001A3: // AUXSPIDATA
|
||||
case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL
|
||||
case 0x4100010: case 0x4100011: case 0x4100012: case 0x4100013: // Slot 1 Data In
|
||||
return Nds.Cartridge.ReadHwio8(false, addr);
|
||||
|
||||
case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME
|
||||
case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE
|
||||
case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF
|
||||
return Nds.HwControl9.ReadHwio8(addr);
|
||||
|
||||
case 0x4000130: case 0x4000131: // KEYINPUT
|
||||
return Nds.Keypad.ReadHwio8(addr);
|
||||
|
||||
case 0x4000204: case 0x4000205: // EXMEMCNT
|
||||
case 0x4000240: case 0x4000241: case 0x4000242: case 0x4000243: // VRAMCNT
|
||||
case 0x4000244: case 0x4000245: case 0x4000246: case 0x4000247: // VRAMCNT, WRAMCNT
|
||||
case 0x4000248: case 0x4000249: // VRAMCNT
|
||||
return Nds.MemoryControl.ReadHwio8Nds9(addr);
|
||||
|
||||
case 0x4000280: case 0x4000281: case 0x4000282: case 0x4000283: // DIVCNT B3
|
||||
case 0x4000290: case 0x4000291: case 0x4000292: case 0x4000293: // DIV_NUMER
|
||||
case 0x4000294: case 0x4000295: case 0x4000296: case 0x4000297: // DIV_NUMER
|
||||
case 0x4000298: case 0x4000299: case 0x400029A: case 0x400029B: // DIV_DENOM
|
||||
case 0x400029C: case 0x400029D: case 0x400029E: case 0x400029F: // DIV_DENOM
|
||||
case 0x40002A0: case 0x40002A1: case 0x40002A2: case 0x40002A3: // DIV_RESULT
|
||||
case 0x40002A4: case 0x40002A5: case 0x40002A6: case 0x40002A7: // DIV_RESULT
|
||||
case 0x40002A8: case 0x40002A9: case 0x40002AA: case 0x40002AB: // DIVREM_RESULT
|
||||
case 0x40002AC: case 0x40002AD: case 0x40002AE: case 0x40002AF: // DIVREM_RESULT
|
||||
case 0x40002B0: case 0x40002B1: // SQRTCNT
|
||||
case 0x40002B4: case 0x40002B5: case 0x40002B6: case 0x40002B7: // SQRT_RESULT
|
||||
case 0x40002B8: case 0x40002B9: case 0x40002BA: case 0x40002BB: // SQRT_PARAM
|
||||
case 0x40002BC: case 0x40002BD: case 0x40002BE: case 0x40002BF: // SQRT_PARAM
|
||||
return Nds.Math.ReadHwio8(addr);
|
||||
|
||||
case 0x4000300:
|
||||
// Console.WriteLine("NDS9 POSTFLG read");
|
||||
return Nds.HwControl9.Postflg;
|
||||
case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1
|
||||
return Nds.ReadHwio8Arm9(addr);
|
||||
}
|
||||
|
||||
// Console.WriteLine($"NDS9: Unmapped MMIO read addr:{Hex(addr, 8)}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void WriteHwio8(bool debug, uint addr, byte val)
|
||||
{
|
||||
if (LogHwioAccesses)
|
||||
{
|
||||
lock (HwioWriteLog) {
|
||||
if ((addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioWriteLog.TryGetValue(addr, out count);
|
||||
HwioWriteLog[addr] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addr >= 0x4000320 && addr < 0x40006A4) // 3D
|
||||
{
|
||||
// Console.Error.WriteLine($"8-bit or 16-bit write to 3D addr:{Hex(addr, 8)} val:{Hex(val, 2)}");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
// Engine A
|
||||
case 0x4000000: case 0x4000001: case 0x4000002: case 0x4000003: // DISPCNT A
|
||||
case 0x4000004: case 0x4000005: // DISPSTAT
|
||||
case 0x4000006: case 0x4000007: // VCOUNT
|
||||
case 0x4000008: case 0x4000009: // BG0CNT
|
||||
case 0x400000A: case 0x400000B: // BG1CNT
|
||||
case 0x400000C: case 0x400000D: // BG2CNT
|
||||
case 0x400000E: case 0x400000F: // BG3CNT
|
||||
case 0x4000010: case 0x4000011: case 0x4000012: case 0x4000013: // BG0OFS
|
||||
case 0x4000014: case 0x4000015: case 0x4000016: case 0x4000017: // BG1OFS
|
||||
case 0x4000018: case 0x4000019: case 0x400001A: case 0x400001B: // BG2OFS
|
||||
case 0x400001C: case 0x400001D: case 0x400001E: case 0x400001F: // BG3OFS
|
||||
case 0x4000020: case 0x4000021: case 0x4000022: case 0x4000023: // BG2PA/PB
|
||||
case 0x4000024: case 0x4000025: case 0x4000026: case 0x4000027: // BG2PC/PD
|
||||
case 0x4000028: case 0x4000029: case 0x400002A: case 0x400002B: // BG2X
|
||||
case 0x400002C: case 0x400002D: case 0x400002E: case 0x400002F: // BG2Y
|
||||
case 0x4000030: case 0x4000031: case 0x4000032: case 0x4000033: // BG3PA/PB
|
||||
case 0x4000034: case 0x4000035: case 0x4000036: case 0x4000037: // BG3PC/PD
|
||||
case 0x4000038: case 0x4000039: case 0x400003A: case 0x400003B: // BG3X
|
||||
case 0x400003C: case 0x400003D: case 0x400003E: case 0x400003F: // BG3Y
|
||||
case 0x4000040: case 0x4000041: case 0x4000042: case 0x4000043: // WINH
|
||||
case 0x4000044: case 0x4000045: case 0x4000046: case 0x4000047: // WINV
|
||||
case 0x4000048: case 0x4000049: case 0x400004A: case 0x400004B: // WININ/OUT
|
||||
case 0x400004C: case 0x400004D: // MOSAIC
|
||||
case 0x4000050: case 0x4000051: // BLDCNT
|
||||
case 0x4000052: case 0x4000053: // BLDALPHA
|
||||
case 0x4000054: case 0x4000055: // BLDY
|
||||
case 0x4000060: case 0x4000061: // DISP3DCNT
|
||||
case 0x4000064: case 0x4000065: case 0x4000066: case 0x4000067: // DISPCAPCNT
|
||||
case 0x400006C: case 0x400006D: // MASTER_BRIGHT
|
||||
|
||||
// Engine B
|
||||
case 0x4001000: case 0x4001001: case 0x4001002: case 0x4001003: // DISPCNT A
|
||||
case 0x4001008: case 0x4001009: // BG0CNT
|
||||
case 0x400100A: case 0x400100B: // BG1CNT
|
||||
case 0x400100C: case 0x400100D: // BG2CNT
|
||||
case 0x400100E: case 0x400100F: // BG3CNT
|
||||
case 0x4001010: case 0x4001011: case 0x4001012: case 0x4001013: // BG0OFS
|
||||
case 0x4001014: case 0x4001015: case 0x4001016: case 0x4001017: // BG1OFS
|
||||
case 0x4001018: case 0x4001019: case 0x400101A: case 0x400101B: // BG2OFS
|
||||
case 0x400101C: case 0x400101D: case 0x400101E: case 0x400101F: // BG3OFS
|
||||
case 0x4001020: case 0x4001021: case 0x4001022: case 0x4001023: // BG2PA/PB
|
||||
case 0x4001024: case 0x4001025: case 0x4001026: case 0x4001027: // BG2PC/PD
|
||||
case 0x4001028: case 0x4001029: case 0x400102A: case 0x400102B: // BG2X
|
||||
case 0x400102C: case 0x400102D: case 0x400102E: case 0x400102F: // BG2Y
|
||||
case 0x4001030: case 0x4001031: case 0x4001032: case 0x4001033: // BG3PA/PB
|
||||
case 0x4001034: case 0x4001035: case 0x4001036: case 0x4001037: // BG3PC/PD
|
||||
case 0x4001038: case 0x4001039: case 0x400103A: case 0x400103B: // BG3X
|
||||
case 0x400103C: case 0x400103D: case 0x400103E: case 0x400103F: // BG3Y
|
||||
case 0x4001040: case 0x4001041: case 0x4001042: case 0x4001043: // WINH
|
||||
case 0x4001044: case 0x4001045: case 0x4001046: case 0x4001047: // WINV
|
||||
case 0x4001048: case 0x4001049: case 0x400104A: case 0x400104B: // WININ/OUT
|
||||
case 0x400104C: case 0x400104D: // MOSAIC
|
||||
case 0x4001050: case 0x4001051: // BLDCNT
|
||||
case 0x4001052: case 0x4001053: // BLDALPHA
|
||||
case 0x4001054: case 0x4001055: // BLDY
|
||||
case 0x400106C: case 0x400106D: // MASTER_BRIGHT
|
||||
Nds.Ppu.WriteHwio8Arm9(addr, val); return;
|
||||
|
||||
case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD
|
||||
case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD
|
||||
case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT
|
||||
case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD
|
||||
case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD
|
||||
case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT
|
||||
case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD
|
||||
case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD
|
||||
case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT
|
||||
case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD
|
||||
case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD
|
||||
case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT
|
||||
case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data
|
||||
case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data
|
||||
case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data
|
||||
case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data
|
||||
Nds.Dma9.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0
|
||||
case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1
|
||||
case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2
|
||||
case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3
|
||||
Nds.Timers9.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC
|
||||
case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT
|
||||
case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND
|
||||
Nds.Ipcs[0].WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x40001A0: case 0x40001A1: // AUXSPICNT
|
||||
case 0x40001A2: case 0x40001A3: // AUXSPIDATA
|
||||
case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL
|
||||
case 0x40001A8: case 0x40001A9: case 0x40001AA: case 0x40001AB: // Slot 1 Command 0-3
|
||||
case 0x40001AC: case 0x40001AD: case 0x40001AE: case 0x40001AF: // Slot 1 Command 4-7
|
||||
Nds.Cartridge.WriteHwio8(false, addr, val); return;
|
||||
|
||||
case 0x40001B0: case 0x40001B1: case 0x40001B2: case 0x40001B3: // Slot 1 KEY2 encryption seed
|
||||
case 0x40001B4: case 0x40001B5: case 0x40001B6: case 0x40001B7:
|
||||
case 0x40001B8: case 0x40001B9: case 0x40001BA: case 0x40001BB:
|
||||
return;
|
||||
|
||||
case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME
|
||||
case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE
|
||||
case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF
|
||||
Nds.HwControl9.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000204: case 0x4000205: // EXMEMCNT
|
||||
case 0x4000240: case 0x4000241: case 0x4000242: case 0x4000243: // VRAMCNT
|
||||
case 0x4000244: case 0x4000245: case 0x4000246: case 0x4000247: // VRAMCNT, WRAMCNT
|
||||
case 0x4000248: case 0x4000249: // VRAMCNT
|
||||
Nds.MemoryControl.WriteHwio8Nds9(addr, val); return;
|
||||
|
||||
case 0x4000280: case 0x4000281: case 0x4000282: case 0x4000283: // DIVCNT B3
|
||||
case 0x4000290: case 0x4000291: case 0x4000292: case 0x4000293: // DIV_NUMER
|
||||
case 0x4000294: case 0x4000295: case 0x4000296: case 0x4000297: // DIV_NUMER
|
||||
case 0x4000298: case 0x4000299: case 0x400029A: case 0x400029B: // DIV_DENOM
|
||||
case 0x400029C: case 0x400029D: case 0x400029E: case 0x400029F: // DIV_DENOM
|
||||
case 0x40002A0: case 0x40002A1: case 0x40002A2: case 0x40002A3: // DIV_RESULT
|
||||
case 0x40002A4: case 0x40002A5: case 0x40002A6: case 0x40002A7: // DIV_RESULT
|
||||
case 0x40002A8: case 0x40002A9: case 0x40002AA: case 0x40002AB: // DIVREM_RESULT
|
||||
case 0x40002AC: case 0x40002AD: case 0x40002AE: case 0x40002AF: // DIVREM_RESULT
|
||||
case 0x40002B0: case 0x40002B1: // SQRTCNT
|
||||
case 0x40002B4: case 0x40002B5: case 0x40002B6: case 0x40002B7: // SQRT_RESULT
|
||||
case 0x40002B8: case 0x40002B9: case 0x40002BA: case 0x40002BB: // SQRT_PARAM
|
||||
case 0x40002BC: case 0x40002BD: case 0x40002BE: case 0x40002BF: // SQRT_PARAM
|
||||
Nds.Math.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000300:
|
||||
Console.WriteLine("NDS9 POSTFLG write");
|
||||
Nds.HwControl9.Postflg = (byte)(val & 0b11);
|
||||
return;
|
||||
case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307:// POWCNT1
|
||||
Nds.WriteHwio8Arm9(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
// Console.WriteLine($"NDS9: Unmapped MMIO write addr:{Hex(addr, 8)} val:{Hex(val, 2)}");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c562e007e77a5c448b8dfa6621f2efb8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d5d8289e96e59342bab0324b6d66a91
|
||||
guid: 13e3364ce9f87224fa1b1bfac909d236
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,396 +0,0 @@
|
||||
using System;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using static OptimeGBA.Bits;
|
||||
using static Util;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public unsafe sealed class Nds
|
||||
{
|
||||
public ProviderNds Provider;
|
||||
|
||||
// ARM9 side
|
||||
public MemoryNds9 Mem9;
|
||||
public Arm7 Cpu9;
|
||||
public HwControlNds HwControl9;
|
||||
public DmaNds Dma9;
|
||||
public Timers Timers9;
|
||||
public Nds9Math Math;
|
||||
|
||||
// ARM7 side
|
||||
public MemoryNds7 Mem7;
|
||||
public Arm7 Cpu7;
|
||||
public HwControlNds HwControl7;
|
||||
public Spi Spi;
|
||||
public NdsAudio Audio;
|
||||
public DmaNds Dma7;
|
||||
public Timers Timers7;
|
||||
|
||||
public Scheduler Scheduler;
|
||||
|
||||
public Cp15 Cp15;
|
||||
|
||||
public CartridgeNds Cartridge;
|
||||
|
||||
// Based off of EXMEMCNT ownership rules, there 1 is ARM7
|
||||
public Ipc[] Ipcs; // 0: ARM9 to ARM7, 1: ARM7 to ARM9
|
||||
|
||||
public PpuNds Ppu;
|
||||
public PpuNds3D Ppu3D;
|
||||
|
||||
public MemoryControlNds MemoryControl;
|
||||
|
||||
public Keypad Keypad = new Keypad();
|
||||
|
||||
public RtcNds Rtc;
|
||||
|
||||
public byte[] MainRam = new byte[4194304];
|
||||
public byte[] SharedRam = new byte[32768];
|
||||
|
||||
public int Arm9PendingTicks;
|
||||
|
||||
public ulong Steps;
|
||||
|
||||
public Nds(ProviderNds provider)
|
||||
{
|
||||
Provider = provider;
|
||||
Scheduler = new Scheduler();
|
||||
|
||||
Ipcs = new Ipc[] {
|
||||
new Ipc(this, 0),
|
||||
new Ipc(this, 1),
|
||||
};
|
||||
|
||||
Cp15 = new Cp15(this);
|
||||
|
||||
Cartridge = new CartridgeNds(this);
|
||||
|
||||
Ppu = new PpuNds(this, Scheduler);
|
||||
Ppu3D = new PpuNds3D(this, Scheduler);
|
||||
|
||||
MemoryControl = new MemoryControlNds();
|
||||
|
||||
Rtc = new RtcNds();
|
||||
|
||||
// ARM9 Init
|
||||
Mem9 = new MemoryNds9(this, Provider);
|
||||
Cpu9 = new Arm7(StateChangeArm9, Mem9, true, true, Cp15);
|
||||
HwControl9 = new HwControlNds(Cpu9);
|
||||
Dma9 = new DmaNds(false, Mem9, HwControl9);
|
||||
Timers9 = new Timers(null, HwControl9, Scheduler, true, false);
|
||||
Math = new Nds9Math(this);
|
||||
Mem9.InitPageTables();
|
||||
Cpu9.InitFlushPipeline();
|
||||
Cpu9.SetVectorMode(true);
|
||||
// screw it
|
||||
Cpu9.SetTimingsTable(
|
||||
Cpu9.Timing8And16,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu9.SetTimingsTable(
|
||||
Cpu9.Timing32,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu9.SetTimingsTable(
|
||||
Cpu9.Timing8And16InstrFetch,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu9.SetTimingsTable(
|
||||
Cpu9.Timing32InstrFetch,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
|
||||
// ARM7 init
|
||||
Mem7 = new MemoryNds7(this, Provider);
|
||||
Spi = new Spi(this);
|
||||
Audio = new NdsAudio(this);
|
||||
Cpu7 = new Arm7(StateChangeArm7, Mem7, false, false, null);
|
||||
HwControl7 = new HwControlNds(Cpu7);
|
||||
Dma7 = new DmaNds(true, Mem7, HwControl7);
|
||||
Timers7 = new Timers(null, HwControl7, Scheduler, true, true);
|
||||
Mem7.InitPageTables();
|
||||
Cpu7.InitFlushPipeline();
|
||||
// screw it
|
||||
Cpu7.SetTimingsTable(
|
||||
Cpu7.Timing8And16,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu7.SetTimingsTable(
|
||||
Cpu7.Timing32,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu7.SetTimingsTable(
|
||||
Cpu7.Timing8And16InstrFetch,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu7.SetTimingsTable(
|
||||
Cpu7.Timing32InstrFetch,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
|
||||
#if UNSAFE
|
||||
Console.WriteLine("Starting in memory UNSAFE mode");
|
||||
#else
|
||||
Console.WriteLine("Starting in memory SAFE mode");
|
||||
#endif
|
||||
|
||||
if (provider.DirectBoot)
|
||||
{
|
||||
var rom = provider.Rom;
|
||||
|
||||
// Firmware init
|
||||
MemoryControl.SharedRamControl = 3;
|
||||
HwControl7.Postflg = 1;
|
||||
HwControl9.Postflg = 1;
|
||||
|
||||
Cartridge.Slot1Enable = true;
|
||||
|
||||
Cpu9.IRQDisable = true;
|
||||
Cpu9.FIQDisable = true;
|
||||
|
||||
// Thanks Hydr8gon / fleroviux lol
|
||||
Mem7.Write16(0x4000184, 0x8501); // IPCFIFOCNT7
|
||||
Mem9.Write16(0x4000184, 0x8501); // IPCFIFOCNT9
|
||||
|
||||
Cp15.TransferTo(0, 0x0005707D, 1, 0, 0); // CP15 Control
|
||||
Cp15.TransferTo(0, 0x0300000A, 9, 1, 0); // Data TCM base/size
|
||||
Cp15.TransferTo(0, 0x00000020, 9, 1, 1); // Instruction TCM size
|
||||
Mem9.Write8(0x4000247, 0x03); // WRAMCNT
|
||||
Mem9.Write16(0x4000304, 0x0001); // POWCNT1
|
||||
Mem7.Write16(0x4000504, 0x0200); // SOUNDBIAS
|
||||
|
||||
Mem9.Write32(0x027FF800, 0x1FC2); // Chip ID 1
|
||||
Mem9.Write32(0x027FF804, 0x1FC2); // Chip ID 2
|
||||
Mem9.Write16(0x027FF850, 0x5835); // ARM7 BIOS CRC
|
||||
Mem9.Write16(0x027FF880, 0x0007); // Message from ARM9 to ARM7
|
||||
Mem9.Write16(0x027FF884, 0x0006); // ARM7 boot task
|
||||
Mem9.Write32(0x027FFC00, 0x1FC2); // Copy of chip ID 1
|
||||
Mem9.Write32(0x027FFC04, 0x1FC2); // Copy of chip ID 2
|
||||
Mem9.Write16(0x027FFC10, 0x5835); // Copy of ARM7 BIOS CRC
|
||||
Mem9.Write16(0x027FFC40, 0x0001); // Boot indicator
|
||||
|
||||
Mem9.Write32(0x027FF864, 0);
|
||||
Mem9.Write32(0x027FF868, (uint)(GetUshort(Provider.Firmware, 0x20) << 3));
|
||||
|
||||
Mem9.Write16(0x027FF874, GetUshort(Provider.Firmware, 0x26));
|
||||
Mem9.Write16(0x027FF876, GetUshort(Provider.Firmware, 0x04));
|
||||
|
||||
// Copy in header
|
||||
if (rom.Length >= 0x170)
|
||||
{
|
||||
for (uint i = 0; i < 0x170; i++)
|
||||
{
|
||||
Mem9.Write8(0x027FFE00 + i, rom[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 0x70; i++)
|
||||
{
|
||||
Mem9.Write8(0x27FFC80 + i, Provider.Firmware[0x3FF00 + i]);
|
||||
}
|
||||
|
||||
Mem9.Write32(0x027FF864, 0);
|
||||
Mem9.Write32(0x027FF868, (uint)(GetUshort(Provider.Firmware, 0x20) << 3));
|
||||
|
||||
Mem9.Write16(0x027FF874, GetUshort(Provider.Firmware, 0x26));
|
||||
Mem9.Write16(0x027FF876, GetUshort(Provider.Firmware, 0x04));
|
||||
|
||||
if (rom.Length >= 0x20)
|
||||
{
|
||||
uint arm7RomOffset = GetUint(rom, 0x30);
|
||||
uint arm7EntryAddr = GetUint(rom, 0x34);
|
||||
uint arm7RamAddr = GetUint(rom, 0x38);
|
||||
uint arm7Size = GetUint(rom, 0x3C);
|
||||
|
||||
// ROM offset is aligned by 0x1000
|
||||
Console.WriteLine("ARM7 ROM Offset: " + Hex(arm7RomOffset, 8));
|
||||
Console.WriteLine("ARM7 RAM Address: " + Hex(arm7RamAddr, 8));
|
||||
Console.WriteLine("ARM7 Entry: " + Hex(arm7EntryAddr, 8));
|
||||
Console.WriteLine("ARM7 Size: " + arm7Size);
|
||||
for (uint i = 0; i < arm7Size; i++)
|
||||
{
|
||||
Mem7.Write8(arm7RamAddr + i, rom[arm7RomOffset + i]);
|
||||
}
|
||||
Cpu7.R[13] = 0x3002F7C;
|
||||
Cpu7.SetModeReg(13, Arm7Mode.IRQ, 0x3003F80);
|
||||
Cpu7.SetModeReg(13, Arm7Mode.SVC, 0x3003FC0);
|
||||
Cpu7.R[12] = arm7EntryAddr;
|
||||
Cpu7.R[14] = arm7EntryAddr;
|
||||
Cpu7.R[15] = arm7EntryAddr;
|
||||
Cpu7.InitFlushPipeline();
|
||||
|
||||
uint arm9RomOffset = GetUint(rom, 0x20);
|
||||
uint arm9EntryAddr = GetUint(rom, 0x24);
|
||||
uint arm9RamAddr = GetUint(rom, 0x28);
|
||||
uint arm9Size = GetUint(rom, 0x2C);
|
||||
|
||||
Console.WriteLine("ARM9 ROM Offset: " + Hex(arm9RomOffset, 8));
|
||||
Console.WriteLine("ARM9 RAM Address: " + Hex(arm9RamAddr, 8));
|
||||
Console.WriteLine("ARM9 Entry: " + Hex(arm9EntryAddr, 8));
|
||||
Console.WriteLine("ARM9 Size: " + arm9Size);
|
||||
for (uint i = 0; i < arm9Size; i++)
|
||||
{
|
||||
Mem9.Write8(arm9RamAddr + i, rom[arm9RomOffset + i]);
|
||||
}
|
||||
Cpu9.R[13] = 0x380FD80;
|
||||
Cpu9.SetModeReg(13, Arm7Mode.IRQ, 0x380FF80);
|
||||
Cpu9.SetModeReg(13, Arm7Mode.SVC, 0x380FFC0);
|
||||
Cpu9.R[12] = arm9EntryAddr;
|
||||
Cpu9.R[14] = arm9EntryAddr;
|
||||
Cpu9.R[15] = arm9EntryAddr;
|
||||
Cpu9.InitFlushPipeline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Step()
|
||||
{
|
||||
Steps++;
|
||||
|
||||
long beforeTicks = Scheduler.CurrentTicks;
|
||||
|
||||
while (Scheduler.CurrentTicks < Scheduler.NextEventTicks)
|
||||
{
|
||||
// Running both CPUs at 1CPI at 32 MHz causes the firmware to loop the setup screen,
|
||||
// so don't do that when not debugging simple test ROMs
|
||||
// Cpu7.Execute();
|
||||
// Cpu9.Execute();
|
||||
// Scheduler.CurrentTicks += 1;
|
||||
|
||||
// TODO: Proper NDS timings
|
||||
// TODO: Figure out a better way to implement halting
|
||||
uint ticks7 = 0;
|
||||
// Run 32 ARM7 instructions at a time, who needs tight synchronization
|
||||
const uint instrsAtATime = 32;
|
||||
if (!Cpu7.Halted)
|
||||
{
|
||||
for (uint i = 0; i < instrsAtATime; i++)
|
||||
{
|
||||
if (!Cpu7.Halted)
|
||||
{
|
||||
ticks7 += Cpu7.Execute();
|
||||
}
|
||||
else
|
||||
{
|
||||
ticks7 += instrsAtATime;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ticks7 += instrsAtATime;
|
||||
}
|
||||
|
||||
Arm9PendingTicks += (int)ticks7 * 2; // ARM9 runs at twice the speed of ARM7
|
||||
while (Arm9PendingTicks > 0)
|
||||
{
|
||||
if (!Cpu9.Halted)
|
||||
{
|
||||
Arm9PendingTicks -= (int)Cpu9.Execute();
|
||||
}
|
||||
else
|
||||
{
|
||||
Arm9PendingTicks -= (int)(Scheduler.NextEventTicks - Scheduler.CurrentTicks) * 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ppu3D.Run();
|
||||
|
||||
Scheduler.CurrentTicks += ticks7;
|
||||
}
|
||||
|
||||
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 Tick(uint cycles)
|
||||
{
|
||||
Scheduler.CurrentTicks += cycles;
|
||||
}
|
||||
|
||||
public void HaltSkip(long cyclesOffset) { }
|
||||
|
||||
// POWCNT1
|
||||
public bool EnableDisplay;
|
||||
public bool Enable2DEngineA;
|
||||
public bool Enable3DRenderingEngine;
|
||||
public bool Enable3DGeometryEngine;
|
||||
public bool Enable2DEngineB;
|
||||
public bool DisplaySwap;
|
||||
|
||||
public byte ReadHwio8Arm9(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000304:
|
||||
if (EnableDisplay) val = BitSet(val, 0);
|
||||
if (Enable2DEngineA) val = BitSet(val, 1);
|
||||
if (Enable3DRenderingEngine) val = BitSet(val, 2);
|
||||
if (Enable3DGeometryEngine) val = BitSet(val, 3);
|
||||
break;
|
||||
case 0x4000305:
|
||||
if (Enable2DEngineB) val = BitSet(val, 1);
|
||||
if (DisplaySwap) val = BitSet(val, 7);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Arm9(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000304:
|
||||
EnableDisplay = BitTest(val, 0);
|
||||
Enable2DEngineA = BitTest(val, 1);
|
||||
Enable3DRenderingEngine = BitTest(val, 2);
|
||||
Enable3DGeometryEngine = BitTest(val, 3);
|
||||
break;
|
||||
case 0x4000305:
|
||||
Enable2DEngineB = BitTest(val, 1);
|
||||
DisplaySwap = BitTest(val, 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void StateChangeArm9() { }
|
||||
|
||||
// POWCNT2
|
||||
public bool EnableSpeakers;
|
||||
public bool EnableWifi;
|
||||
|
||||
public byte ReadHwio8Arm7(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000304:
|
||||
if (EnableSpeakers) val = BitSet(val, 0);
|
||||
if (EnableWifi) val = BitSet(val, 1);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Arm7(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000304:
|
||||
EnableSpeakers = BitTest(val, 0);
|
||||
EnableWifi = BitTest(val, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void StateChangeArm7() { }
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4cdddf1f287aa24d91a6aa89a4bae70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,276 +0,0 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public unsafe sealed class Nds9Math
|
||||
{
|
||||
public Nds Nds;
|
||||
|
||||
public Nds9Math(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
}
|
||||
|
||||
public long DIV_NUMER;
|
||||
public long DIV_DENOM;
|
||||
|
||||
public long DIV_RESULT;
|
||||
public long DIVREM_RESULT;
|
||||
|
||||
public uint SQRT_RESULT;
|
||||
public ulong SQRT_PARAM;
|
||||
|
||||
// DIVCNT
|
||||
public uint DivisionMode;
|
||||
public bool DividedByZero;
|
||||
public bool DivideBusy;
|
||||
|
||||
// SQRTCNT
|
||||
public bool SqrtUse64BitInput;
|
||||
public bool SqrtBusy;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000280: // DIVCNT B0
|
||||
val |= (byte)(DivisionMode & 0b11);
|
||||
break;
|
||||
case 0x4000281: // DIVCNT B1
|
||||
if (DividedByZero) val = BitSet(val, 6);
|
||||
if (DivideBusy) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x4000282: // DIVCNT B2
|
||||
case 0x4000283: // DIVCNT B3
|
||||
break;
|
||||
|
||||
case 0x4000290: // DIV_NUMER B0
|
||||
case 0x4000291: // DIV_NUMER B1
|
||||
case 0x4000292: // DIV_NUMER B2
|
||||
case 0x4000293: // DIV_NUMER B3
|
||||
case 0x4000294: // DIV_NUMER B4
|
||||
case 0x4000295: // DIV_NUMER B5
|
||||
case 0x4000296: // DIV_NUMER B6
|
||||
case 0x4000297: // DIV_NUMER B7
|
||||
return GetByteIn(DIV_NUMER, addr & 7);
|
||||
case 0x4000298: // DIV_DENOM B0
|
||||
case 0x4000299: // DIV_DENOM B1
|
||||
case 0x400029A: // DIV_DENOM B2
|
||||
case 0x400029B: // DIV_DENOM B3
|
||||
case 0x400029C: // DIV_DENOM B4
|
||||
case 0x400029D: // DIV_DENOM B5
|
||||
case 0x400029E: // DIV_DENOM B6
|
||||
case 0x400029F: // DIV_DENOM B7
|
||||
return GetByteIn(DIV_DENOM, addr & 7);
|
||||
case 0x40002A0: // DIV_RESULT B0
|
||||
case 0x40002A1: // DIV_RESULT B1
|
||||
case 0x40002A2: // DIV_RESULT B2
|
||||
case 0x40002A3: // DIV_RESULT B3
|
||||
case 0x40002A4: // DIV_RESULT B4
|
||||
case 0x40002A5: // DIV_RESULT B5
|
||||
case 0x40002A6: // DIV_RESULT B6
|
||||
case 0x40002A7: // DIV_RESULT B7
|
||||
return GetByteIn(DIV_RESULT, addr & 7);
|
||||
case 0x40002A8: // DIVREM_RESULT B0
|
||||
case 0x40002A9: // DIVREM_RESULT B1
|
||||
case 0x40002AA: // DIVREM_RESULT B2
|
||||
case 0x40002AB: // DIVREM_RESULT B3
|
||||
case 0x40002AC: // DIVREM_RESULT B4
|
||||
case 0x40002AD: // DIVREM_RESULT B5
|
||||
case 0x40002AE: // DIVREM_RESULT B6
|
||||
case 0x40002AF: // DIVREM_RESULT B7
|
||||
return GetByteIn(DIVREM_RESULT, addr & 7);
|
||||
|
||||
case 0x40002B0: // SQRTCNT B0
|
||||
if (SqrtUse64BitInput) val = BitSet(val, 0);
|
||||
break;
|
||||
case 0x40002B1: // SQRTCNT B0
|
||||
break;
|
||||
|
||||
case 0x40002B4: // SQRT_RESULT B0
|
||||
case 0x40002B5: // SQRT_RESULT B1
|
||||
case 0x40002B6: // SQRT_RESULT B2
|
||||
case 0x40002B7: // SQRT_RESULT B3
|
||||
return GetByteIn(SQRT_RESULT, addr & 3);
|
||||
case 0x40002B8: // SQRT_PARAM B0
|
||||
case 0x40002B9: // SQRT_PARAM B1
|
||||
case 0x40002BA: // SQRT_PARAM B2
|
||||
case 0x40002BB: // SQRT_PARAM B3
|
||||
case 0x40002BC: // SQRT_PARAM B4
|
||||
case 0x40002BD: // SQRT_PARAM B5
|
||||
case 0x40002BE: // SQRT_PARAM B6
|
||||
case 0x40002BF: // SQRT_PARAM B7
|
||||
return GetByteIn(SQRT_PARAM, addr & 7);
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Read from DS math @ " + Util.Hex(addr, 8));
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000280: // DIVCNT B0
|
||||
DivisionMode = (byte)(val & 0b11);
|
||||
Divide();
|
||||
break;
|
||||
case 0x4000281: // DIVCNT B1
|
||||
case 0x4000282: // DIVCNT B2
|
||||
case 0x4000283: // DIVCNT B3
|
||||
break;
|
||||
|
||||
case 0x4000290: // DIV_NUMER B0
|
||||
case 0x4000291: // DIV_NUMER B1
|
||||
case 0x4000292: // DIV_NUMER B2
|
||||
case 0x4000293: // DIV_NUMER B3
|
||||
case 0x4000294: // DIV_NUMER B4
|
||||
case 0x4000295: // DIV_NUMER B5
|
||||
case 0x4000296: // DIV_NUMER B6
|
||||
case 0x4000297: // DIV_NUMER B7
|
||||
DIV_NUMER = SetByteIn(DIV_NUMER, val, addr & 7);
|
||||
Divide();
|
||||
break;
|
||||
case 0x4000298: // DIV_DENOM B0
|
||||
case 0x4000299: // DIV_DENOM B1
|
||||
case 0x400029A: // DIV_DENOM B2
|
||||
case 0x400029B: // DIV_DENOM B3
|
||||
case 0x400029C: // DIV_DENOM B4
|
||||
case 0x400029D: // DIV_DENOM B5
|
||||
case 0x400029E: // DIV_DENOM B6
|
||||
case 0x400029F: // DIV_DENOM B7
|
||||
DIV_DENOM = SetByteIn(DIV_DENOM, val, addr & 7);
|
||||
Divide();
|
||||
break;
|
||||
|
||||
case 0x40002B0: // SQRTCNT B0
|
||||
SqrtUse64BitInput = BitTest(val, 0);
|
||||
TakeSquareRoot();
|
||||
break;
|
||||
case 0x40002B1: // SQRTCNT B0
|
||||
break;
|
||||
|
||||
case 0x40002B8: // SQRT_PARAM B0
|
||||
case 0x40002B9: // SQRT_PARAM B1
|
||||
case 0x40002BA: // SQRT_PARAM B2
|
||||
case 0x40002BB: // SQRT_PARAM B3
|
||||
case 0x40002BC: // SQRT_PARAM B4
|
||||
case 0x40002BD: // SQRT_PARAM B5
|
||||
case 0x40002BE: // SQRT_PARAM B6
|
||||
case 0x40002BF: // SQRT_PARAM B7
|
||||
SQRT_PARAM = SetByteIn(SQRT_PARAM, val, addr & 7);
|
||||
TakeSquareRoot();
|
||||
return;
|
||||
|
||||
// default:
|
||||
// throw new NotImplementedException("Write to DS math @ " + Util.Hex(addr, 8));
|
||||
}
|
||||
}
|
||||
|
||||
public void Divide()
|
||||
{
|
||||
DividedByZero = DIV_DENOM == 0;
|
||||
|
||||
switch (DivisionMode)
|
||||
{
|
||||
case 0: // 32bit / 32bit
|
||||
if ((int)DIV_NUMER == int.MinValue && (int)DIV_DENOM == -1) // Overflow
|
||||
{
|
||||
DIV_RESULT = (long)(int)DIV_NUMER ^ (0xFFFFFFFFL << 32);
|
||||
DIVREM_RESULT = 0;
|
||||
}
|
||||
else if ((int)DIV_DENOM != 0)
|
||||
{
|
||||
DIV_RESULT = (int)DIV_NUMER / (int)DIV_DENOM;
|
||||
DIVREM_RESULT = (int)DIV_NUMER % (int)DIV_DENOM;
|
||||
}
|
||||
else // Division by 0
|
||||
{
|
||||
DIV_RESULT = (((int)DIV_NUMER < 0) ? 1 : -1) ^ (0xFFFFFFFFL << 32);
|
||||
DIVREM_RESULT = (int)DIV_NUMER;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
case 1: // 64bit / 32bit
|
||||
if (DIV_NUMER == long.MinValue && (int)DIV_DENOM == -1) // Overflow
|
||||
{
|
||||
DIV_RESULT = DIV_NUMER;
|
||||
DIVREM_RESULT = 0;
|
||||
}
|
||||
else if ((int)DIV_DENOM != 0)
|
||||
{
|
||||
DIV_RESULT = DIV_NUMER / (int)DIV_DENOM;
|
||||
DIVREM_RESULT = DIV_NUMER % (int)DIV_DENOM;
|
||||
}
|
||||
else // Division by 0
|
||||
{
|
||||
DIV_RESULT = (DIV_NUMER < 0) ? 1 : -1;
|
||||
DIVREM_RESULT = DIV_NUMER;
|
||||
}
|
||||
break;
|
||||
case 2: // 64bit / 64bit
|
||||
if (DIV_NUMER == long.MinValue && DIV_DENOM == -1) // Overflow
|
||||
{
|
||||
DIV_RESULT = DIV_NUMER;
|
||||
DIVREM_RESULT = 0;
|
||||
}
|
||||
else if (DIV_DENOM != 0)
|
||||
{
|
||||
DIV_RESULT = DIV_NUMER / DIV_DENOM;
|
||||
DIVREM_RESULT = DIV_NUMER % DIV_DENOM;
|
||||
}
|
||||
else // Division by 0
|
||||
{
|
||||
DIV_RESULT = (DIV_NUMER < 0) ? 1 : -1;
|
||||
DIVREM_RESULT = DIV_NUMER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Console.WriteLine("Divison Mode: " + DivisionMode);
|
||||
// Console.WriteLine("Numerator : " + DIV_NUMER);
|
||||
// Console.WriteLine("Demoninator: " + DIV_DENOM);
|
||||
// Console.WriteLine("Result : " + DIV_RESULT);
|
||||
// Console.WriteLine("Remainder : " + DIVREM_RESULT);
|
||||
}
|
||||
|
||||
public void TakeSquareRoot()
|
||||
{
|
||||
if (SqrtUse64BitInput)
|
||||
{
|
||||
ulong val = SQRT_PARAM;
|
||||
|
||||
uint final = 0;
|
||||
ulong rem = 0;
|
||||
uint prod = 0;
|
||||
|
||||
const uint nbits = 32;
|
||||
const int topShift = 62;
|
||||
|
||||
for (int i = 0; i < nbits; i++)
|
||||
{
|
||||
rem = (rem << 2) + ((val >> topShift) & 0x3);
|
||||
val <<= 2;
|
||||
final <<= 1;
|
||||
|
||||
prod = (final << 1) + 1;
|
||||
if (rem >= prod)
|
||||
{
|
||||
rem -= prod;
|
||||
final++;
|
||||
}
|
||||
}
|
||||
|
||||
SQRT_RESULT = final;
|
||||
}
|
||||
else
|
||||
{
|
||||
SQRT_RESULT = (uint)Math.Sqrt((uint)SQRT_PARAM);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 969688026ca7e194dbc2b0acc79a84d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,525 +0,0 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public class CustomSample
|
||||
{
|
||||
public short[] Data; // In PCM16
|
||||
public uint LoopPoint; // In samples
|
||||
public uint RepeatMode;
|
||||
|
||||
public CustomSample(short[] data, uint loopPoint, uint repeatMode)
|
||||
{
|
||||
Data = data;
|
||||
LoopPoint = loopPoint;
|
||||
RepeatMode = repeatMode;
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioChannelNds
|
||||
{
|
||||
// SOUNDxCNT
|
||||
public uint Volume;
|
||||
public byte VolumeDiv;
|
||||
public bool Hold;
|
||||
public byte Pan;
|
||||
public byte PulseDuty;
|
||||
public byte RepeatMode;
|
||||
public byte Format;
|
||||
public bool Playing;
|
||||
|
||||
public uint SOUNDSAD;
|
||||
public uint SOUNDTMR;
|
||||
public uint SOUNDPNT;
|
||||
public uint SOUNDLEN;
|
||||
|
||||
public uint SamplePos;
|
||||
public uint Timer;
|
||||
|
||||
public uint Interval;
|
||||
public int CurrentValue;
|
||||
public int AdpcmIndex;
|
||||
public int AdpcmLoopValue;
|
||||
public int AdpcmLoopIndex;
|
||||
public uint AdpcmLoopCurrentData;
|
||||
public uint CurrentData;
|
||||
|
||||
public bool DebugEnable = true;
|
||||
public uint DebugAdpcmSaved;
|
||||
public uint DebugAdpcmRestored;
|
||||
|
||||
public long DebugStartTicks;
|
||||
}
|
||||
|
||||
public class NdsAudio
|
||||
{
|
||||
Nds Nds;
|
||||
|
||||
public NdsAudio(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
|
||||
for (uint i = 0; i < 16; i++)
|
||||
{
|
||||
Channels[i] = new AudioChannelNds();
|
||||
}
|
||||
|
||||
Sample(0);
|
||||
}
|
||||
|
||||
public AudioChannelNds[] Channels = new AudioChannelNds[16];
|
||||
|
||||
public const int SampleRate = 32768;
|
||||
|
||||
public bool EnableBlipBufResampling = true;
|
||||
public BlipBuf BlipBuf = new BlipBuf(32, true, 16);
|
||||
|
||||
public bool Record = false;
|
||||
public WavWriter WavWriter = new WavWriter(SampleRate);
|
||||
public WavWriter WavWriterSinc = new WavWriter(SampleRate);
|
||||
|
||||
public int SampleTimer = 0;
|
||||
|
||||
public const uint SampleBufferMax = 256;
|
||||
public short[] SampleBuffer = new short[SampleBufferMax];
|
||||
public uint SampleBufferPos = 0;
|
||||
|
||||
public static sbyte[] IndexTable = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
public static short[] AdpcmTable = {
|
||||
0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x0010, 0x0011, 0x0013, 0x0015,
|
||||
0x0017, 0x0019, 0x001C, 0x001F, 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
|
||||
0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F, 0x009D, 0x00AD, 0x00BE, 0x00D1,
|
||||
0x00E6, 0x00FD, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
|
||||
0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, 0x0610, 0x06AB, 0x0756, 0x0812,
|
||||
0x08E0, 0x09C3, 0x0ABD, 0x0BD0, 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
|
||||
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, 0x3BB9, 0x41B2, 0x4844, 0x4F7E,
|
||||
0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF
|
||||
};
|
||||
|
||||
// SOUNDCNT
|
||||
public byte MasterVolume;
|
||||
public uint LeftOutputFrom;
|
||||
public uint RightOutputFrom;
|
||||
public bool Ch1ToMixer;
|
||||
public bool Ch3ToMixer;
|
||||
public bool MasterEnable;
|
||||
|
||||
// SOUNDBIAS
|
||||
public ushort SOUNDBIAS;
|
||||
|
||||
// SNDCAPCNT
|
||||
byte SNDCAP0CNT;
|
||||
byte SNDCAP1CNT;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000500: // SOUNDCNT B0
|
||||
val |= (byte)(MasterVolume & 0x7FU);
|
||||
break;
|
||||
case 0x4000501: // SOUNDCNT B1
|
||||
val |= (byte)((LeftOutputFrom & 0b11) << 0);
|
||||
val |= (byte)((RightOutputFrom & 0b11) << 2);
|
||||
if (Ch1ToMixer) val = BitSet(val, 4);
|
||||
if (Ch3ToMixer) val = BitSet(val, 5);
|
||||
if (MasterEnable) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4000504:
|
||||
return (byte)(SOUNDBIAS >> 0);
|
||||
case 0x4000505:
|
||||
return (byte)(SOUNDBIAS >> 8);
|
||||
|
||||
case 0x4000508:
|
||||
return SNDCAP0CNT;
|
||||
case 0x4000509:
|
||||
return SNDCAP1CNT;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000500: // SOUNDCNT B0
|
||||
MasterVolume = (byte)(val & 0x7FU);
|
||||
break;
|
||||
case 0x4000501: // SOUNDCNT B1
|
||||
LeftOutputFrom = (byte)((val >> 0) & 0b11);
|
||||
RightOutputFrom = (byte)((val >> 2) & 0b11);
|
||||
Ch1ToMixer = BitTest(val, 4);
|
||||
Ch3ToMixer = BitTest(val, 5);
|
||||
MasterEnable = BitTest(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4000504:
|
||||
SOUNDBIAS &= 0xFF00;
|
||||
SOUNDBIAS |= (ushort)(val << 0);
|
||||
break;
|
||||
case 0x4000505:
|
||||
SOUNDBIAS &= 0x00FF;
|
||||
SOUNDBIAS |= (ushort)(val << 8);
|
||||
break;
|
||||
|
||||
case 0x4000508:
|
||||
SNDCAP0CNT = val;
|
||||
break;
|
||||
case 0x4000509:
|
||||
SNDCAP1CNT = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public byte ReadHwio8Channels(uint addr)
|
||||
{
|
||||
var c = Channels[(addr >> 4) & 0xF];
|
||||
|
||||
byte val = 0;
|
||||
|
||||
switch (addr & 0xF)
|
||||
{
|
||||
case 0x0:
|
||||
val |= (byte)(c.Volume & 0x7F);
|
||||
break;
|
||||
case 0x1:
|
||||
val |= c.VolumeDiv;
|
||||
if (c.Hold) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x2:
|
||||
val |= c.Pan;
|
||||
break;
|
||||
case 0x3:
|
||||
val |= c.PulseDuty;
|
||||
val |= (byte)(c.RepeatMode << 3);
|
||||
val |= (byte)(c.Format << 5);
|
||||
if (c.Playing) val = BitSet(val, 7);
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Channels(uint addr, byte val)
|
||||
{
|
||||
var c = Channels[(addr >> 4) & 0xF];
|
||||
|
||||
switch (addr & 0xF)
|
||||
{
|
||||
case 0x0:
|
||||
c.Volume = (byte)(val & 0x7F);
|
||||
break;
|
||||
case 0x1:
|
||||
c.VolumeDiv = (byte)(val & 3);
|
||||
c.Hold = BitTest(val, 7);
|
||||
break;
|
||||
case 0x2:
|
||||
c.Pan = (byte)(val & 0x7F);
|
||||
break;
|
||||
case 0x3:
|
||||
c.PulseDuty = (byte)(val & 7);
|
||||
c.RepeatMode = (byte)((val >> 3) & 3);
|
||||
c.Format = (byte)((val >> 5) & 3);
|
||||
if (!c.Playing && BitTest(val, 7))
|
||||
{
|
||||
StartChannel(c);
|
||||
}
|
||||
c.Playing = BitTest(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4:
|
||||
case 0x5:
|
||||
case 0x6:
|
||||
case 0x7:
|
||||
Console.WriteLine(Util.Hex(Nds.Cpu7.GetCurrentInstrAddr(), 8));
|
||||
// Console.WriteLine(Nds.MemoryControl.SharedRamControl);
|
||||
|
||||
c.SOUNDSAD = SetByteIn(c.SOUNDSAD, val, addr & 3) & 0x7FFFFFC;
|
||||
|
||||
if (c.Playing)
|
||||
{
|
||||
StartChannel(c);
|
||||
}
|
||||
break;
|
||||
case 0x8:
|
||||
case 0x9:
|
||||
c.SOUNDTMR = SetByteIn(c.SOUNDTMR, val, addr & 1);
|
||||
c.Interval = 2 * (0x10000 - c.SOUNDTMR);
|
||||
break;
|
||||
case 0xA:
|
||||
case 0xB:
|
||||
c.SOUNDPNT = SetByteIn(c.SOUNDPNT, val, addr & 1);
|
||||
break;
|
||||
case 0xC:
|
||||
case 0xD:
|
||||
case 0xE:
|
||||
case 0xF:
|
||||
c.SOUNDLEN = SetByteIn(c.SOUNDLEN, val, addr & 3) & 0x3FFFFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void StartChannel(AudioChannelNds c)
|
||||
{
|
||||
c.SamplePos = 0;
|
||||
c.Timer = 0;
|
||||
c.CurrentValue = 0;
|
||||
|
||||
c.DebugStartTicks = Nds.Scheduler.CurrentTicks;
|
||||
}
|
||||
|
||||
public void Sample(long cyclesLate)
|
||||
{
|
||||
long left = 0;
|
||||
long right = 0;
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
var c = Channels[i];
|
||||
|
||||
if (c.Playing)
|
||||
{
|
||||
c.Timer += 1024;
|
||||
while (c.Timer >= c.Interval && c.Interval != 0)
|
||||
{
|
||||
c.Timer -= c.Interval;
|
||||
|
||||
// Advance sample
|
||||
switch (c.Format)
|
||||
{
|
||||
case 0: // PCM8
|
||||
if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 4)
|
||||
{
|
||||
switch (c.RepeatMode)
|
||||
{
|
||||
case 1: // Infinite
|
||||
c.SamplePos = c.SOUNDPNT * 4;
|
||||
break;
|
||||
case 2: // One-shot
|
||||
c.Playing = false;
|
||||
if (!c.Hold)
|
||||
{
|
||||
c.CurrentValue = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((c.SamplePos & 3) == 0)
|
||||
{
|
||||
c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos);
|
||||
}
|
||||
|
||||
c.CurrentValue = (short)((byte)c.CurrentData << 8);
|
||||
c.CurrentData >>= 8;
|
||||
|
||||
c.SamplePos++;
|
||||
break;
|
||||
case 1: // PCM16
|
||||
if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 2)
|
||||
{
|
||||
switch (c.RepeatMode)
|
||||
{
|
||||
case 1: // Infinite
|
||||
c.SamplePos = c.SOUNDPNT * 2;
|
||||
break;
|
||||
case 2: // One-shot
|
||||
c.Playing = false;
|
||||
if (!c.Hold)
|
||||
{
|
||||
c.CurrentValue = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((c.SamplePos & 1) == 0)
|
||||
{
|
||||
c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos * 2);
|
||||
}
|
||||
|
||||
c.CurrentValue = (short)c.CurrentData;
|
||||
c.CurrentData >>= 16;
|
||||
|
||||
c.SamplePos++;
|
||||
break;
|
||||
case 2: // IMA-ADPCM
|
||||
if ((c.SamplePos & 7) == 0)
|
||||
{
|
||||
c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos / 2);
|
||||
// ADPCM header
|
||||
if (c.SamplePos == 0)
|
||||
{
|
||||
c.CurrentValue = (short)c.CurrentData;
|
||||
// Console.WriteLine("header set " + x++);
|
||||
// Console.WriteLine("interval: " + Util.Hex(c.Interval, 8));
|
||||
c.AdpcmIndex = Math.Clamp((int)(c.CurrentData >> 16), 0, 88);
|
||||
}
|
||||
// Console.WriteLine("addr: " + Util.Hex(c.Source, 8));
|
||||
}
|
||||
if (c.SamplePos > 7)
|
||||
{
|
||||
// End of sound, loop or stop
|
||||
if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 8)
|
||||
{
|
||||
switch (c.RepeatMode)
|
||||
{
|
||||
case 1: // Infinite
|
||||
c.SamplePos = c.SOUNDPNT * 8;
|
||||
c.CurrentValue = c.AdpcmLoopValue;
|
||||
c.AdpcmIndex = c.AdpcmLoopIndex;
|
||||
c.CurrentData = c.AdpcmLoopCurrentData;
|
||||
// Console.WriteLine($"Ch{i}: Loaded at " + c.SampleNum);
|
||||
|
||||
c.DebugAdpcmRestored = c.SamplePos;
|
||||
break;
|
||||
case 2: // One-shot
|
||||
c.Playing = false;
|
||||
if (!c.Hold)
|
||||
{
|
||||
c.CurrentValue = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byte data = (byte)(c.CurrentData & 0xF);
|
||||
|
||||
short tableVal = AdpcmTable[c.AdpcmIndex];
|
||||
int diff = tableVal / 8;
|
||||
if ((data & 1) != 0) diff += tableVal / 4;
|
||||
if ((data & 2) != 0) diff += tableVal / 2;
|
||||
if ((data & 4) != 0) diff += tableVal / 1;
|
||||
|
||||
if ((data & 8) == 8)
|
||||
{
|
||||
c.CurrentValue = Math.Max((int)c.CurrentValue - diff, -0x7FFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
c.CurrentValue = Math.Min((int)c.CurrentValue + diff, 0x7FFF);
|
||||
}
|
||||
c.AdpcmIndex = Math.Clamp(c.AdpcmIndex + IndexTable[data & 7], 0, 88);
|
||||
|
||||
c.CurrentData >>= 4;
|
||||
|
||||
// Save value and ADPCM table index for loop
|
||||
if (c.SamplePos == c.SOUNDPNT * 8)
|
||||
{
|
||||
c.AdpcmLoopValue = c.CurrentValue;
|
||||
c.AdpcmLoopIndex = c.AdpcmIndex;
|
||||
c.AdpcmLoopCurrentData = c.CurrentData;
|
||||
|
||||
c.DebugAdpcmSaved = c.SamplePos;
|
||||
// Console.WriteLine($"Ch{i}: Saved at " + c.SampleNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
c.SamplePos++;
|
||||
break;
|
||||
case 3: // Pulse / Noise
|
||||
if (((c.SamplePos ^ 7) & 7) <= c.PulseDuty)
|
||||
{
|
||||
c.CurrentValue = -0x7FFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
c.CurrentValue = 0x7FFF;
|
||||
}
|
||||
c.SamplePos++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (EnableBlipBufResampling)
|
||||
{
|
||||
long timeTicks = Nds.Scheduler.CurrentTicks - cyclesLate + 1024 - (c.Timer % 1024);
|
||||
double timeSec = (double)timeTicks / 33513982D;
|
||||
double timeSample = (double)timeTicks / (33513982D / (double)SampleRate);
|
||||
|
||||
if (c.DebugEnable)
|
||||
{
|
||||
uint effectiveVol = c.Volume;
|
||||
if (effectiveVol == 127) effectiveVol++;
|
||||
long leftCh = ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * (127 - c.Pan)) >> 10;
|
||||
long rightCh = ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * c.Pan) >> 10;
|
||||
|
||||
BlipBuf.SetValue(i, timeSample, leftCh, rightCh);
|
||||
}
|
||||
else
|
||||
{
|
||||
BlipBuf.SetValue(i, timeSample, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c.DebugEnable)
|
||||
{
|
||||
uint effectiveVol = c.Volume;
|
||||
if (effectiveVol == 127) effectiveVol++;
|
||||
left += ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * (127 - c.Pan)) >> 10;
|
||||
right += ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * c.Pan) >> 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decimate samples to 32768 hz
|
||||
// Since 33513982 hz / 1024 ≅ 32728.498 hz
|
||||
SampleTimer += SampleRate * 1024;
|
||||
while (SampleTimer >= 33513982)
|
||||
{
|
||||
SampleTimer -= 33513982;
|
||||
|
||||
BlipBuf.ReadOutSample();
|
||||
|
||||
// 28 bits now, after mixing all channels
|
||||
// add master volume to get 35 bits
|
||||
// add
|
||||
// strip 19 to get 16 bits for our short output
|
||||
uint effectiveMasterVol = MasterVolume;
|
||||
if (effectiveMasterVol == 127) effectiveMasterVol++;
|
||||
|
||||
short leftFinalSinc = (short)(((long)BlipBuf.CurrentValL * effectiveMasterVol) >> 16);
|
||||
short rightFinalSinc = (short)(((long)BlipBuf.CurrentValR * effectiveMasterVol) >> 16);
|
||||
|
||||
short leftFinal = (short)((left * effectiveMasterVol) >> 16);
|
||||
short rightFinal = (short)((right * effectiveMasterVol) >> 16);
|
||||
|
||||
if (EnableBlipBufResampling)
|
||||
{
|
||||
SampleBuffer[SampleBufferPos++] = leftFinalSinc;
|
||||
SampleBuffer[SampleBufferPos++] = rightFinalSinc;
|
||||
}
|
||||
else
|
||||
{
|
||||
SampleBuffer[SampleBufferPos++] = leftFinal;
|
||||
SampleBuffer[SampleBufferPos++] = rightFinal;
|
||||
}
|
||||
|
||||
if (Record)
|
||||
{
|
||||
WavWriterSinc.AddSample(leftFinalSinc, rightFinalSinc);
|
||||
WavWriter.AddSample(leftFinal, rightFinal);
|
||||
}
|
||||
|
||||
if (SampleBufferPos >= SampleBufferMax)
|
||||
{
|
||||
SampleBufferPos = 0;
|
||||
|
||||
Nds.Provider.AudioCallback(SampleBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
Nds.Scheduler.AddEventRelative(SchedulerId.ApuSample, 1024 - cyclesLate, Sample);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aac09fde41176a14b888ed3e2d5d4676
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
117
Assets/emulator/PipeStream.cs
Normal file
117
Assets/emulator/PipeStream.cs
Normal 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(); }
|
||||
}
|
||||
}
|
11
Assets/emulator/PipeStream.cs.meta
Normal file
11
Assets/emulator/PipeStream.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 177ba151dcfeb3046a3b7a0db12fec0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01c48e13c41f22a45ab867df39d272b6
|
||||
guid: 11d62ad7b3245cc4bbce8635df978afc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -16,7 +16,7 @@ namespace OptimeGBA
|
||||
{
|
||||
Gba = gba;
|
||||
Scheduler = scheduler;
|
||||
Renderer = new PpuRenderer(null, 240, 160);
|
||||
Renderer = new PpuRenderer(240, 160);
|
||||
|
||||
Scheduler.AddEventRelative(SchedulerId.Ppu, 960, EndDrawingToHblank);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03dc9232a0ce9c44ea7bda048c1ff906
|
||||
guid: ebff50650dda02d42a9df8547d689656
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,925 +0,0 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed unsafe class PpuNds
|
||||
{
|
||||
Nds Nds;
|
||||
Scheduler Scheduler;
|
||||
|
||||
public PpuRenderer[] Renderers;
|
||||
|
||||
public PpuNds(Nds nds, Scheduler scheduler)
|
||||
{
|
||||
Nds = nds;
|
||||
Scheduler = scheduler;
|
||||
Renderers = new PpuRenderer[] {
|
||||
new PpuRenderer(nds, 256, 192),
|
||||
new PpuRenderer(nds, 256, 192)
|
||||
};
|
||||
|
||||
Scheduler.AddEventRelative(SchedulerId.Ppu, 1536, EndDrawingToHblank);
|
||||
}
|
||||
|
||||
// Raw VRAM Blocks
|
||||
public byte[] VramA = new byte[131072];
|
||||
public byte[] VramB = new byte[131072];
|
||||
public byte[] VramC = new byte[131072];
|
||||
public byte[] VramD = new byte[131072];
|
||||
public byte[] VramE = new byte[65536];
|
||||
public byte[] VramF = new byte[16384];
|
||||
public byte[] VramG = new byte[16384];
|
||||
public byte[] VramH = new byte[32768];
|
||||
public byte[] VramI = new byte[16384];
|
||||
|
||||
// Built arrays (Passed to PpuRenderer for rendering)
|
||||
public byte[] VramLcdc = new byte[671744];
|
||||
public byte[] VramBgA = new byte[524288];
|
||||
public byte[] VramObjA = new byte[262144];
|
||||
public byte[] VramBgB = new byte[131072];
|
||||
public byte[] VramObjB = new byte[131072];
|
||||
|
||||
public bool DebugDisableVramUpdates;
|
||||
|
||||
public byte ReadVram8Arm9(uint addr)
|
||||
{
|
||||
switch (addr & 0xFFE00000)
|
||||
{
|
||||
case 0x06000000: // Engine A BG VRAM
|
||||
return ReadVram8Arm9BgA(addr);
|
||||
case 0x06200000: // Engine B BG VRAM
|
||||
return ReadVram8Arm9BgB(addr);
|
||||
case 0x06400000: // Engine A OBJ VRAM
|
||||
return ReadVram8Arm9ObjA(addr);
|
||||
case 0x06600000: // Engine B OBJ VRAM
|
||||
return ReadVram8Arm9ObjB(addr);
|
||||
case 0x06800000: // LCDC VRAM
|
||||
return ReadVram8Arm9Lcdc(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public byte ReadVram8Arm9BgA(uint addr)
|
||||
{
|
||||
addr &= 0x1FFFFF;
|
||||
byte val = 0;
|
||||
uint offs = Nds.MemoryControl.GetOffset(0) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 1))
|
||||
{
|
||||
val |= VramA[addr & 0x1FFFF];
|
||||
}
|
||||
offs = Nds.MemoryControl.GetOffset(1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 1))
|
||||
{
|
||||
val |= VramB[addr & 0x1FFFF];
|
||||
}
|
||||
offs = Nds.MemoryControl.GetOffset(2) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 1))
|
||||
{
|
||||
val |= VramC[addr & 0x1FFFF];
|
||||
}
|
||||
offs = Nds.MemoryControl.GetOffset(3) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 1))
|
||||
{
|
||||
val |= VramD[addr & 0x1FFFF];
|
||||
}
|
||||
if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 1))
|
||||
{
|
||||
val |= VramE[addr & 0xFFFF];
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000;
|
||||
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 1))
|
||||
{
|
||||
val |= VramF[addr & 0x3FFF];
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000;
|
||||
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 1))
|
||||
{
|
||||
val |= VramG[addr & 0x3FFF];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public byte ReadVram8Arm9BgB(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
addr &= 0x1FFFFF;
|
||||
if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(2, 4))
|
||||
{
|
||||
val |= VramC[addr & 0x1FFFF];
|
||||
}
|
||||
if (addr < 0x8000 && Nds.MemoryControl.VramEnabledAndSet(7, 1))
|
||||
{
|
||||
val |= VramH[addr & 0x7FFF];
|
||||
}
|
||||
if (addr >= 0x8000 && addr < 0xC000 && Nds.MemoryControl.VramEnabledAndSet(8, 1))
|
||||
{
|
||||
val |= VramI[addr & 0x3FFF];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public byte ReadVram8Arm9ObjA(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
addr &= 0x1FFFFF;
|
||||
uint offs = (Nds.MemoryControl.GetOffset(0) & 1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 2))
|
||||
{
|
||||
val |= VramA[addr & 0x1FFFF];
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(1) & 1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 2))
|
||||
{
|
||||
val |= VramB[addr & 0x1FFFF];
|
||||
}
|
||||
if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 2))
|
||||
{
|
||||
val |= VramE[addr & 0xFFFF];
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000;
|
||||
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 2))
|
||||
{
|
||||
val |= VramF[addr & 0x3FFF];
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000;
|
||||
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 2))
|
||||
{
|
||||
val |= VramG[addr & 0x3FFF];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public byte ReadVram8Arm9ObjB(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
addr &= 0x1FFFFF;
|
||||
if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(3, 4))
|
||||
{
|
||||
val |= VramD[addr & 0x1FFFF];
|
||||
}
|
||||
if (addr < 0x4000 && Nds.MemoryControl.VramEnabledAndSet(8, 2))
|
||||
{
|
||||
val |= VramI[addr & 0x3FFF];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public byte ReadVram8Arm9Lcdc(uint addr)
|
||||
{
|
||||
switch (addr & 0xE0000)
|
||||
{
|
||||
case 0x00000: // A
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(0, 0))
|
||||
return VramA[addr & 0x1FFFF];
|
||||
return 0;
|
||||
case 0x20000: // B
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(1, 0))
|
||||
return VramB[addr & 0x1FFFF];
|
||||
return 0;
|
||||
case 0x40000: // C
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(2, 0))
|
||||
return VramC[addr & 0x1FFFF];
|
||||
return 0;
|
||||
case 0x60000: // D
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(3, 0))
|
||||
return VramD[addr & 0x1FFFF];
|
||||
return 0;
|
||||
case 0x80000: // E, F, G, H
|
||||
switch (addr & 0xFF000)
|
||||
{
|
||||
case 0x00000:
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(4, 0))
|
||||
return VramE[addr & 0xFFFF];
|
||||
return 0;
|
||||
case 0x90000: // F
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(5, 0))
|
||||
return VramF[addr & 0x3FFF];
|
||||
return 0;
|
||||
case 0x94000: // G
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(6, 0))
|
||||
return VramG[addr & 0x3FFF];
|
||||
return 0;
|
||||
case 0x98000: // H
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(7, 0))
|
||||
return VramH[addr & 0x7FFF];
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case 0x8A000: // I
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(8, 0))
|
||||
return VramI[addr & 0x3FFF];
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void WriteVram8Arm9(uint addr, byte val)
|
||||
{
|
||||
uint offs;
|
||||
byte readVal = 0;
|
||||
switch (addr & 0xFFE00000)
|
||||
{
|
||||
case 0x06000000: // Engine A BG VRAM
|
||||
addr &= 0x1FFFFF;
|
||||
offs = Nds.MemoryControl.GetOffset(0) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 1))
|
||||
{
|
||||
readVal |= val; VramA[addr & 0x1FFFF] = val;
|
||||
}
|
||||
offs = Nds.MemoryControl.GetOffset(1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 1))
|
||||
{
|
||||
readVal |= val; VramB[addr & 0x1FFFF] = val;
|
||||
}
|
||||
offs = Nds.MemoryControl.GetOffset(2) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 1))
|
||||
{
|
||||
readVal |= val; VramC[addr & 0x1FFFF] = val;
|
||||
}
|
||||
offs = Nds.MemoryControl.GetOffset(3) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 1))
|
||||
{
|
||||
readVal |= val; VramD[addr & 0x1FFFF] = val;
|
||||
}
|
||||
if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 1))
|
||||
{
|
||||
readVal |= val; VramE[addr & 0xFFFF] = val;
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000;
|
||||
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 1))
|
||||
{
|
||||
readVal |= val; VramF[addr & 0x3FFF] = val;
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000;
|
||||
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 1))
|
||||
{
|
||||
readVal |= val; VramG[addr & 0x3FFF] = val;
|
||||
}
|
||||
VramBgA[addr & 0x1FFFFF] = readVal;
|
||||
break;
|
||||
case 0x06200000: // Engine B BG VRAM
|
||||
addr &= 0x1FFFFF;
|
||||
if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(2, 4))
|
||||
{
|
||||
readVal |= val; VramC[addr & 0x1FFFF] = val;
|
||||
}
|
||||
if (addr < 0x8000 && Nds.MemoryControl.VramEnabledAndSet(7, 1))
|
||||
{
|
||||
readVal |= val; VramH[addr & 0x7FFF] = val;
|
||||
}
|
||||
if (addr >= 0x8000 && addr < 0xC000 && Nds.MemoryControl.VramEnabledAndSet(8, 1))
|
||||
{
|
||||
readVal |= val; VramI[addr & 0x3FFF] = val;
|
||||
}
|
||||
VramBgB[addr & 0x1FFFF] = readVal;
|
||||
break;
|
||||
case 0x06400000: // Engine A OBJ VRAM
|
||||
addr &= 0x1FFFFF;
|
||||
offs = (Nds.MemoryControl.GetOffset(0) & 1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 2))
|
||||
{
|
||||
readVal |= val; VramA[addr & 0x1FFFF] = val;
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(1) & 1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 2))
|
||||
{
|
||||
readVal |= val; VramB[addr & 0x1FFFF] = val;
|
||||
}
|
||||
if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 2))
|
||||
{
|
||||
readVal |= val; VramE[addr & 0xFFFF] = val;
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000;
|
||||
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 2))
|
||||
{
|
||||
readVal |= val; VramF[addr & 0x3FFF] = val;
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000;
|
||||
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 2))
|
||||
{
|
||||
readVal |= val; VramG[addr & 0x3FFF] = val;
|
||||
}
|
||||
VramObjA[addr & 0xFFFFF] = readVal;
|
||||
break;
|
||||
case 0x06600000: // Engine B OBJ VRAM
|
||||
addr &= 0x1FFFFF;
|
||||
if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(3, 4))
|
||||
{
|
||||
readVal |= val; VramD[addr & 0x1FFFF] = val;
|
||||
}
|
||||
if (addr < 0x4000 && Nds.MemoryControl.VramEnabledAndSet(8, 2))
|
||||
{
|
||||
readVal |= val; VramI[addr & 0x3FFF] = val;
|
||||
}
|
||||
VramObjB[addr & 0x1FFFF] = readVal;
|
||||
break;
|
||||
case 0x06800000: // LCDC VRAM
|
||||
switch (addr & 0xFFFE0000)
|
||||
{
|
||||
case 0x06800000: // A
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(0, 0))
|
||||
readVal |= val; VramA[addr & 0x1FFFF] = val;
|
||||
break;
|
||||
case 0x06820000: // B
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(1, 0))
|
||||
readVal |= val; VramB[addr & 0x1FFFF] = val;
|
||||
break;
|
||||
case 0x06840000: // C
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(2, 0))
|
||||
readVal |= val; VramC[addr & 0x1FFFF] = val;
|
||||
break;
|
||||
case 0x06860000: // D
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(3, 0))
|
||||
readVal |= val; VramD[addr & 0x1FFFF] = val;
|
||||
break;
|
||||
case 0x06880000: // E, F, G, H
|
||||
switch (addr & 0xFFFFF000)
|
||||
{
|
||||
case 0x68800000:
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(4, 0))
|
||||
readVal |= val; VramE[addr & 0xFFFF] = val;
|
||||
break;
|
||||
case 0x06890000: // F
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(5, 0))
|
||||
readVal |= val; VramF[addr & 0x3FFF] = val;
|
||||
break;
|
||||
case 0x06894000: // G
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(6, 0))
|
||||
readVal |= val; VramG[addr & 0x3FFF] = val;
|
||||
break;
|
||||
case 0x06898000: // H
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(7, 0))
|
||||
readVal |= val; VramH[addr & 0x7FFF] = val;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0x068A0000: // I
|
||||
if (Nds.MemoryControl.VramEnabledAndSet(8, 0))
|
||||
readVal |= val; VramI[addr & 0x3FFF] = val;
|
||||
break;
|
||||
}
|
||||
addr &= 0xFFFFF;
|
||||
if (addr < 671744)
|
||||
{
|
||||
VramLcdc[addr] = readVal;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public byte ReadVram8Arm7(uint addr)
|
||||
{
|
||||
uint offs;
|
||||
byte val = 0;
|
||||
addr &= 0x1FFFFF;
|
||||
offs = (Nds.MemoryControl.GetOffset(2) & 1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 2))
|
||||
{
|
||||
val |= VramC[addr & 0x1FFFF];
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(3) & 1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 2))
|
||||
{
|
||||
val |= VramD[addr & 0x1FFFF];
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteVram8Arm7(uint addr, byte val)
|
||||
{
|
||||
uint offs;
|
||||
addr &= 0x1FFFFF;
|
||||
offs = (Nds.MemoryControl.GetOffset(2) & 1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 2))
|
||||
{
|
||||
VramC[addr & 0x1FFFF] = val;
|
||||
}
|
||||
offs = (Nds.MemoryControl.GetOffset(3) & 1) * 0x20000;
|
||||
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 2))
|
||||
{
|
||||
VramD[addr & 0x1FFFF] = val;
|
||||
}
|
||||
}
|
||||
|
||||
public void CompileVram()
|
||||
{
|
||||
if (Nds.MemoryControl.VramConfigDirty && !DebugDisableVramUpdates)
|
||||
{
|
||||
Nds.MemoryControl.VramConfigDirty = false;
|
||||
if (Renderers[0].DisplayMode == 2) // LCDC MODE
|
||||
{
|
||||
uint index = 0;
|
||||
VramA.CopyTo(VramLcdc, index); index += 131072;
|
||||
VramB.CopyTo(VramLcdc, index); index += 131072;
|
||||
VramC.CopyTo(VramLcdc, index); index += 131072;
|
||||
VramD.CopyTo(VramLcdc, index); index += 131072;
|
||||
VramE.CopyTo(VramLcdc, index); index += 65536;
|
||||
VramF.CopyTo(VramLcdc, index); index += 16384;
|
||||
VramG.CopyTo(VramLcdc, index); index += 16384;
|
||||
VramH.CopyTo(VramLcdc, index); index += 32768;
|
||||
VramI.CopyTo(VramLcdc, index); index += 16384;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("VRAM reconfigured, recompiling from scratch");
|
||||
for (uint i = 0; i < 524288; i++)
|
||||
{
|
||||
VramBgA[i] = ReadVram8Arm9(0x06000000 + i);
|
||||
}
|
||||
for (uint i = 0; i < 262144; i++)
|
||||
{
|
||||
VramObjA[i] = ReadVram8Arm9(0x06400000 + i);
|
||||
}
|
||||
for (uint i = 0; i < 131072; i++)
|
||||
{
|
||||
VramBgB[i] = ReadVram8Arm9(0x06200000 + i);
|
||||
}
|
||||
for (uint i = 0; i < 131072; i++)
|
||||
{
|
||||
VramObjB[i] = ReadVram8Arm9(0x06600000 + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long ScanlineStartCycles;
|
||||
|
||||
public uint DISPCNTAValue;
|
||||
public uint DISPCNTBValue;
|
||||
|
||||
// DISPSTAT
|
||||
public bool VCounterMatch7;
|
||||
public bool VBlankIrqEnable7;
|
||||
public bool HBlankIrqEnable7;
|
||||
public bool VCounterIrqEnable7;
|
||||
public uint VCountSetting7;
|
||||
public bool VCounterMatch9;
|
||||
public bool VBlankIrqEnable9;
|
||||
public bool HBlankIrqEnable9;
|
||||
public bool VCounterIrqEnable9;
|
||||
public uint VCountSetting9;
|
||||
|
||||
// State
|
||||
public uint VCount;
|
||||
|
||||
public long GetScanlineCycles()
|
||||
{
|
||||
return Scheduler.CurrentTicks - ScanlineStartCycles;
|
||||
}
|
||||
|
||||
public byte ReadHwio8Arm9(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000000: // DISPCNTA B0
|
||||
return (byte)(DISPCNTAValue >> 0);
|
||||
case 0x4000001: // DISPCNTA B1
|
||||
return (byte)(DISPCNTAValue >> 8);
|
||||
case 0x4000002: // DISPCNTA B2
|
||||
return (byte)(DISPCNTAValue >> 16);
|
||||
case 0x4000003: // DISPCNTA B3
|
||||
return (byte)(DISPCNTAValue >> 24);
|
||||
|
||||
case 0x4000004: // DISPSTAT B0
|
||||
// Vblank flag is set in scanlines 192-261, not including 262 for some reason
|
||||
if (VCount >= 192 && VCount <= 261) val = BitSet(val, 0);
|
||||
// Hblank flag is set at cycle 1606, not cycle 1536
|
||||
if (GetScanlineCycles() >= 1606) val = BitSet(val, 1);
|
||||
if (VCounterMatch9) val = BitSet(val, 2);
|
||||
if (VBlankIrqEnable9) val = BitSet(val, 3);
|
||||
if (HBlankIrqEnable9) val = BitSet(val, 4);
|
||||
if (VCounterIrqEnable9) val = BitSet(val, 5);
|
||||
val |= (byte)((VCountSetting9 >> 1) & 0x80);
|
||||
return val;
|
||||
case 0x4000005: // DISPSTAT B1
|
||||
val |= (byte)VCountSetting9;
|
||||
return val;
|
||||
|
||||
case 0x4000006: // VCOUNT B0 - B1 only exists for Nintendo DS
|
||||
val |= (byte)VCount;
|
||||
return val;
|
||||
case 0x4000007:
|
||||
val |= (byte)((VCount >> 8) & 1);
|
||||
return val;
|
||||
|
||||
case 0x4001000: // DISPCNTB B0
|
||||
return (byte)(DISPCNTBValue >> 0);
|
||||
case 0x4001001: // DISPCNTB B1
|
||||
return (byte)(DISPCNTBValue >> 8);
|
||||
case 0x4001002: // DISPCNTB B2
|
||||
return (byte)(DISPCNTBValue >> 16);
|
||||
case 0x4001003: // DISPCNTB B3
|
||||
return (byte)(DISPCNTBValue >> 24);
|
||||
}
|
||||
|
||||
if (addr >= 0x4000000 && addr < 0x4000058)
|
||||
{
|
||||
return Renderers[0].ReadHwio8(addr & 0xFF);
|
||||
}
|
||||
if (addr >= 0x4001000 && addr < 0x4001058)
|
||||
{
|
||||
return Renderers[1].ReadHwio8(addr & 0xFF);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Arm9(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
// A lot of these DISPCNT values are shared between A/B.
|
||||
case 0x4000000: // DISPCNT B0
|
||||
// A
|
||||
Renderers[0].Bg0Is3D = BitTest(val, 3);
|
||||
|
||||
// A+B
|
||||
Renderers[0].BgMode = BitRange(val, 0, 2);
|
||||
Renderers[0].ObjCharOneDimensional = BitTest(val, 4);
|
||||
Renderers[0].BitmapObjShape = BitTest(val, 5);
|
||||
Renderers[0].BitmapObjMapping = BitTest(val, 6);
|
||||
Renderers[0].ForcedBlank = BitTest(val, 7);
|
||||
|
||||
Renderers[0].BackgroundSettingsDirty = true;
|
||||
|
||||
DISPCNTAValue &= 0xFFFFFF00;
|
||||
DISPCNTAValue |= (uint)(val << 0);
|
||||
|
||||
break;
|
||||
case 0x4000001: // DISPCNT B1
|
||||
// A+B
|
||||
Renderers[0].ScreenDisplayBg[0] = BitTest(val, 8 - 8);
|
||||
Renderers[0].ScreenDisplayBg[1] = BitTest(val, 9 - 8);
|
||||
Renderers[0].ScreenDisplayBg[2] = BitTest(val, 10 - 8);
|
||||
Renderers[0].ScreenDisplayBg[3] = BitTest(val, 11 - 8);
|
||||
Renderers[0].ScreenDisplayObj = BitTest(val, 12 - 8);
|
||||
Renderers[0].Window0DisplayFlag = BitTest(val, 13 - 8);
|
||||
Renderers[0].Window1DisplayFlag = BitTest(val, 14 - 8);
|
||||
Renderers[0].ObjWindowDisplayFlag = BitTest(val, 15 - 8);
|
||||
Renderers[0].AnyWindowEnabled = (val & 0b11100000) != 0;
|
||||
|
||||
Renderers[0].BackgroundSettingsDirty = true;
|
||||
|
||||
DISPCNTAValue &= 0xFFFF00FF;
|
||||
DISPCNTAValue |= (uint)(val << 8);
|
||||
break;
|
||||
case 0x4000002: // DISPCNT B2
|
||||
// A
|
||||
Renderers[0].LcdcVramBlock = BitRange(val, 2, 3);
|
||||
Renderers[0].BitmapObj1DBoundary = BitTest(val, 6);
|
||||
|
||||
// A+B
|
||||
// var oldDisplayMode = Renderers[0].DisplayMode;
|
||||
// if (Renderers[0].DisplayMode != oldDisplayMode) VramDirty = true;
|
||||
Renderers[0].DisplayMode = BitRange(val, 0, 1);
|
||||
Renderers[0].TileObj1DBoundary = BitRange(val, 4, 5);
|
||||
Renderers[0].HBlankIntervalFree = BitTest(val, 7);
|
||||
|
||||
Renderers[0].BackgroundSettingsDirty = true;
|
||||
|
||||
DISPCNTAValue &= 0xFF00FFFF;
|
||||
DISPCNTAValue |= (uint)(val << 16);
|
||||
break;
|
||||
case 0x4000003: // DISPCNT B3
|
||||
// A
|
||||
Renderers[0].CharBaseBlockCoarse = BitRange(val, 0, 2);
|
||||
Renderers[0].MapBaseBlockCoarse = BitRange(val, 3, 5);
|
||||
|
||||
// A+B
|
||||
Renderers[0].BgExtendedPalettes = BitTest(val, 6);
|
||||
Renderers[0].ObjExtendedPalettes = BitTest(val, 7);
|
||||
|
||||
DISPCNTAValue &= 0x00FFFFFF;
|
||||
DISPCNTAValue |= (uint)(val << 24);
|
||||
break;
|
||||
|
||||
case 0x4000004: // DISPSTAT B0
|
||||
VBlankIrqEnable9 = BitTest(val, 3);
|
||||
HBlankIrqEnable9 = BitTest(val, 4);
|
||||
VCounterIrqEnable9 = BitTest(val, 5);
|
||||
|
||||
VCountSetting9 &= 0x0FFU;
|
||||
VCountSetting9 |= (uint)((val & 0x80) << 1);
|
||||
break;
|
||||
case 0x4000005: // DISPSTAT B1
|
||||
VCountSetting9 &= 0x100U;
|
||||
VCountSetting9 |= val;
|
||||
break;
|
||||
|
||||
case 0x4000006: // Vcount
|
||||
case 0x4000007:
|
||||
// throw new NotImplementedException("NDS: write to vcount");
|
||||
break;
|
||||
|
||||
case 0x4001000: // DISPCNTB B0
|
||||
// A+B
|
||||
Renderers[1].BgMode = BitRange(val, 0, 2);
|
||||
Renderers[1].ObjCharOneDimensional = BitTest(val, 4);
|
||||
Renderers[1].BitmapObjShape = BitTest(val, 5);
|
||||
Renderers[1].BitmapObjMapping = BitTest(val, 6);
|
||||
Renderers[1].ForcedBlank = BitTest(val, 7);
|
||||
|
||||
Renderers[1].BackgroundSettingsDirty = true;
|
||||
|
||||
DISPCNTBValue &= 0xFFFFFF00;
|
||||
DISPCNTBValue |= (uint)(val << 0);
|
||||
|
||||
break;
|
||||
case 0x4001001: // DISPCNTB B1
|
||||
// A+B
|
||||
Renderers[1].ScreenDisplayBg[0] = BitTest(val, 8 - 8);
|
||||
Renderers[1].ScreenDisplayBg[1] = BitTest(val, 9 - 8);
|
||||
Renderers[1].ScreenDisplayBg[2] = BitTest(val, 10 - 8);
|
||||
Renderers[1].ScreenDisplayBg[3] = BitTest(val, 11 - 8);
|
||||
Renderers[1].ScreenDisplayObj = BitTest(val, 12 - 8);
|
||||
Renderers[1].Window0DisplayFlag = BitTest(val, 13 - 8);
|
||||
Renderers[1].Window1DisplayFlag = BitTest(val, 14 - 8);
|
||||
Renderers[1].ObjWindowDisplayFlag = BitTest(val, 15 - 8);
|
||||
Renderers[1].AnyWindowEnabled = (val & 0b11100000) != 0;
|
||||
|
||||
Renderers[1].BackgroundSettingsDirty = true;
|
||||
|
||||
DISPCNTBValue &= 0xFFFF00FF;
|
||||
DISPCNTBValue |= (uint)(val << 8);
|
||||
break;
|
||||
case 0x4001002: // DISPCNTB B2
|
||||
// A+B
|
||||
// var oldDisplayModeB = Renderers[1].DisplayMode;
|
||||
// if (Renderers[1].DisplayMode != oldDisplayModeB) VramDirty = true;
|
||||
Renderers[1].DisplayMode = BitRange(val, 0, 1);
|
||||
Renderers[1].TileObj1DBoundary = BitRange(val, 4, 5);
|
||||
Renderers[1].HBlankIntervalFree = BitTest(val, 7);
|
||||
|
||||
Renderers[1].BackgroundSettingsDirty = true;
|
||||
|
||||
DISPCNTBValue &= 0xFF00FFFF;
|
||||
DISPCNTBValue |= (uint)(val << 16);
|
||||
break;
|
||||
case 0x4001003: // DISPCNTB B3
|
||||
// A+B
|
||||
Renderers[1].BgExtendedPalettes = BitTest(val, 6);
|
||||
Renderers[1].ObjExtendedPalettes = BitTest(val, 7);
|
||||
|
||||
DISPCNTBValue &= 0x00FFFFFF;
|
||||
DISPCNTBValue |= (uint)(val << 24);
|
||||
break;
|
||||
}
|
||||
|
||||
if (addr >= 0x4000000 && addr < 0x4000058)
|
||||
{
|
||||
Renderers[0].WriteHwio8(addr & 0xFF, val);
|
||||
}
|
||||
if (addr >= 0x4001000 && addr < 0x4001058)
|
||||
{
|
||||
Renderers[1].WriteHwio8(addr & 0xFF, val);
|
||||
}
|
||||
}
|
||||
|
||||
public byte ReadHwio8Arm7(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000004: // DISPSTAT B0
|
||||
// Vblank flag is set in scanlines 192-261, not including 262 for some reason
|
||||
if (VCount >= 192 && VCount <= 261) val = BitSet(val, 0);
|
||||
// Hblank flag is set at cycle 1606, not cycle 1536
|
||||
if (GetScanlineCycles() >= 1606) val = BitSet(val, 1);
|
||||
if (VCounterMatch7) val = BitSet(val, 2);
|
||||
if (VBlankIrqEnable7) val = BitSet(val, 3);
|
||||
if (HBlankIrqEnable7) val = BitSet(val, 4);
|
||||
if (VCounterIrqEnable7) val = BitSet(val, 5);
|
||||
val |= (byte)((VCountSetting7 >> 1) & 0x80);
|
||||
return val;
|
||||
case 0x4000005: // DISPSTAT B1
|
||||
val |= (byte)VCountSetting7;
|
||||
return val;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void WriteHwio8Arm7(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000004: // DISPSTAT B0
|
||||
VBlankIrqEnable7 = BitTest(val, 3);
|
||||
HBlankIrqEnable7 = BitTest(val, 4);
|
||||
VCounterIrqEnable7 = BitTest(val, 5);
|
||||
|
||||
VCountSetting7 &= 0x0FFU;
|
||||
VCountSetting7 |= (uint)((val & 0x80) << 1);
|
||||
break;
|
||||
case 0x4000005: // DISPSTAT B1
|
||||
VCountSetting7 &= 0x100U;
|
||||
VCountSetting7 |= val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public byte ReadOam8(uint addr)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
return GetByte(Renderers[id].Oam, addr);
|
||||
}
|
||||
|
||||
public ushort ReadOam16(uint addr)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
return GetUshort(Renderers[id].Oam, addr);
|
||||
}
|
||||
|
||||
public uint ReadOam32(uint addr)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
return GetUint(Renderers[id].Oam, addr);
|
||||
}
|
||||
|
||||
public void WriteOam16(uint addr, ushort val)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
SetUshort(Renderers[id].Oam, addr, val);
|
||||
}
|
||||
|
||||
public void WriteOam32(uint addr, uint val)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
SetUint(Renderers[id].Oam, addr, val);
|
||||
}
|
||||
|
||||
public byte ReadPalettes8(uint addr)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
return GetByte(Renderers[id].Palettes, addr);
|
||||
}
|
||||
|
||||
public ushort ReadPalettes16(uint addr)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
return GetUshort(Renderers[id].Palettes, addr);
|
||||
}
|
||||
|
||||
public uint ReadPalettes32(uint addr)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
return GetUint(Renderers[id].Palettes, addr);
|
||||
}
|
||||
|
||||
public void WritePalettes16(uint addr, ushort val)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
if (GetUshort(Renderers[id].Palettes, addr) != val)
|
||||
{
|
||||
SetUshort(Renderers[id].Palettes, addr, val);
|
||||
}
|
||||
}
|
||||
|
||||
public void WritePalettes32(uint addr, uint val)
|
||||
{
|
||||
addr &= 0x7FF;
|
||||
var id = addr >= 0x400 ? 1 : 0;
|
||||
addr &= 0x3FF;
|
||||
if (GetUint(Renderers[id].Palettes, addr) != val)
|
||||
{
|
||||
SetUint(Renderers[id].Palettes, addr, val);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndDrawingToHblank(long cyclesLate)
|
||||
{
|
||||
Scheduler.AddEventRelative(SchedulerId.Ppu, 594 - cyclesLate, EndHblank);
|
||||
|
||||
// if (HBlankIrqEnable)
|
||||
// {
|
||||
// Gba.HwControl.FlagInterrupt(InterruptGba.HBlank);
|
||||
// }
|
||||
|
||||
if (Renderers[0].DisplayMode == 2) // LCDC MODE
|
||||
{
|
||||
Renderers[0].RenderScanlineNds(VCount, VramLcdc, VramLcdc);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Renderers[0].DebugEnableRendering) Renderers[0].RenderScanlineNds(VCount, VramBgA, VramObjA);
|
||||
}
|
||||
if (Renderers[1].DisplayMode == 2)
|
||||
{
|
||||
Renderers[1].RenderScanlineNds(VCount, VramLcdc, VramLcdc);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Renderers[1].DebugEnableRendering) Renderers[1].RenderScanlineNds(VCount, VramBgB, VramObjB);
|
||||
}
|
||||
Renderers[0].IncrementMosaicCounters();
|
||||
Renderers[1].IncrementMosaicCounters();
|
||||
|
||||
Nds.Dma9.Repeat((byte)DmaStartTimingNds9.HBlank);
|
||||
}
|
||||
|
||||
public void EndVblankToHblank(long cyclesLate)
|
||||
{
|
||||
Scheduler.AddEventRelative(SchedulerId.Ppu, 594 - cyclesLate, EndHblank);
|
||||
|
||||
// if (HBlankIrqEnable)
|
||||
// {
|
||||
// Nds.HwControl.FlagInterrupt(InterruptGba.HBlank);
|
||||
// }
|
||||
}
|
||||
|
||||
public void EndHblank(long cyclesLate)
|
||||
{
|
||||
ScanlineStartCycles = Scheduler.CurrentTicks;
|
||||
|
||||
if (VCount != 262)
|
||||
{
|
||||
VCount++;
|
||||
|
||||
if (VCount > 191)
|
||||
{
|
||||
Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndVblankToHblank);
|
||||
|
||||
if (VCount == 192)
|
||||
{
|
||||
// Nds.Dma.RepeatVblank();
|
||||
|
||||
if (VBlankIrqEnable7)
|
||||
{
|
||||
Nds.HwControl7.FlagInterrupt((uint)InterruptNds.VBlank);
|
||||
}
|
||||
if (VBlankIrqEnable9)
|
||||
{
|
||||
Nds.HwControl9.FlagInterrupt((uint)InterruptNds.VBlank);
|
||||
}
|
||||
|
||||
Renderers[0].RunVblankOperations();
|
||||
Renderers[1].RunVblankOperations();
|
||||
|
||||
Renderers[0].TotalFrames++;
|
||||
if (Renderers[0].DebugEnableRendering) Renderers[0].SwapBuffers();
|
||||
if (Renderers[1].DebugEnableRendering) Renderers[1].SwapBuffers();
|
||||
|
||||
Renderers[0].RenderingDone = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndDrawingToHblank);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VCount = 0;
|
||||
Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndDrawingToHblank);
|
||||
|
||||
// CompileVram();
|
||||
|
||||
// Pre-render sprites for line zero
|
||||
fixed (byte* vramObjA = VramObjA, vramObjB = VramObjB)
|
||||
{
|
||||
if (Renderers[0].DebugEnableObj && Renderers[0].ScreenDisplayObj) Renderers[0].RenderObjs(0, vramObjA);
|
||||
if (Renderers[1].DebugEnableObj && Renderers[1].ScreenDisplayObj) Renderers[1].RenderObjs(0, vramObjB);
|
||||
}
|
||||
}
|
||||
|
||||
VCounterMatch7 = VCount == VCountSetting7;
|
||||
VCounterMatch9 = VCount == VCountSetting9;
|
||||
|
||||
if (VCounterMatch7 && VCounterIrqEnable7)
|
||||
{
|
||||
Nds.HwControl7.FlagInterrupt((uint)InterruptNds.VCounterMatch);
|
||||
}
|
||||
if (VCounterMatch9 && VCounterIrqEnable9)
|
||||
{
|
||||
Nds.HwControl9.FlagInterrupt((uint)InterruptNds.VCounterMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94cef7d2d800ba548a03c0fb676340ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c1d666a4057424428483e53a56bdc1c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,32 +1,34 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static MyStruct;
|
||||
using Avx2 = MyStruct;
|
||||
using static OptimeGBA.Bits;
|
||||
using static OptimeGBA.CoreUtil;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using Unity.Burst.Intrinsics;
|
||||
using static Unity.Burst.Intrinsics.X86;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed unsafe class PpuRenderer
|
||||
{
|
||||
public int Width;
|
||||
public int Height;
|
||||
public Nds Nds;
|
||||
public PpuRenderer(Nds nds, int width, int height)
|
||||
public PpuRenderer(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Nds = nds;
|
||||
|
||||
Backgrounds = new Background[4] {
|
||||
new Background(Nds != null, 0),
|
||||
new Background(Nds != null, 1),
|
||||
new Background(Nds != null, 2),
|
||||
new Background(Nds != null, 3),
|
||||
new Background(false, 0),
|
||||
new Background(false, 1),
|
||||
new Background(false, 2),
|
||||
new Background(false, 3),
|
||||
};
|
||||
|
||||
Array.Fill(DebugEnableBg, true);
|
||||
//Array.Fill(DebugEnableBg, true);
|
||||
for (int i = 0; i < DebugEnableBg.Length; i++)
|
||||
{
|
||||
DebugEnableBg[i] = true;
|
||||
}
|
||||
|
||||
int ScreenBufferSize = Width * Height;
|
||||
#if UNSAFE
|
||||
@ -54,14 +56,14 @@ namespace OptimeGBA
|
||||
ScreenBack[i] = 0x7FFF;
|
||||
}
|
||||
|
||||
if (nds == null)
|
||||
//if (nds == null)
|
||||
{
|
||||
DisplayMode = 1;
|
||||
}
|
||||
|
||||
// Load 3D placeholder
|
||||
// Why do I waste time on useless crap like this
|
||||
Stream img = typeof(PpuRenderer).Assembly.GetManifestResourceStream("OptimeGBA-OpenTK.resources.3d-placeholder.raw");
|
||||
/*Stream img = typeof(PpuRenderer).Assembly.GetManifestResourceStream("OptimeGBA-OpenTK.resources.3d-placeholder.raw");
|
||||
if (img == null)
|
||||
{
|
||||
img = typeof(PpuRenderer).Assembly.GetManifestResourceStream("OptimeGBA-SDL.resources.3d-placeholder.raw");
|
||||
@ -86,7 +88,7 @@ namespace OptimeGBA
|
||||
b >>= 3;
|
||||
|
||||
PlaceholderFor3D[index++] = (ushort)((b << 10) | (g << 5) | r);
|
||||
}
|
||||
}*/
|
||||
|
||||
for (uint i = 0; i < Width; i++)
|
||||
WinMasks[i + 8] = 0b111111;
|
||||
@ -316,43 +318,6 @@ namespace OptimeGBA
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderScanlineNds(uint vcount, byte[] bgVramArr, byte[] objVramArr)
|
||||
{
|
||||
if (!ForcedBlank)
|
||||
{
|
||||
fixed (byte* bgVram = bgVramArr, objVram = objVramArr)
|
||||
{
|
||||
switch (DisplayMode)
|
||||
{
|
||||
case 1: // Regular rendering
|
||||
PrepareBackgroundAndWindow(vcount);
|
||||
RenderBgModes(vcount, bgVram);
|
||||
|
||||
if (DebugForce3DLayer) {
|
||||
uint srcBase = (uint)(vcount * Width);
|
||||
|
||||
for (uint i = 0; i < Width; i++)
|
||||
{
|
||||
BgLo[i + 8] = BgHi[i + 8];
|
||||
BgHi[i + 8] = (uint)(Nds.Ppu3D.Screen[srcBase + i]);
|
||||
}
|
||||
}
|
||||
|
||||
Composite(vcount);
|
||||
if (DebugEnableObj && ScreenDisplayObj && vcount != 191) RenderObjs(vcount + 1, objVram);
|
||||
break;
|
||||
case 2: // LCDC Mode
|
||||
RenderMode3(vcount, bgVram);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderWhiteScanline(vcount);
|
||||
}
|
||||
}
|
||||
|
||||
public void RunVblankOperations()
|
||||
{
|
||||
Backgrounds[2].CopyAffineParams();
|
||||
@ -493,51 +458,16 @@ namespace OptimeGBA
|
||||
Backgrounds[1].Mode = BackgroundMode.Char;
|
||||
Backgrounds[2].Mode = BackgroundMode.Char;
|
||||
Backgrounds[3].Mode = BackgroundMode.Char;
|
||||
if (Nds == null)
|
||||
{
|
||||
switch (BgMode)
|
||||
{
|
||||
case 1:
|
||||
Backgrounds[2].Mode = BackgroundMode.Affine;
|
||||
break;
|
||||
case 2:
|
||||
Backgrounds[2].Mode = BackgroundMode.Affine;
|
||||
Backgrounds[3].Mode = BackgroundMode.Affine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Bg0Is3D)
|
||||
{
|
||||
Backgrounds[0].Mode = BackgroundMode.Display3D;
|
||||
}
|
||||
|
||||
switch (BgMode)
|
||||
{
|
||||
case 1:
|
||||
Backgrounds[3].Mode = BackgroundMode.Affine;
|
||||
break;
|
||||
case 2:
|
||||
Backgrounds[2].Mode = BackgroundMode.Affine;
|
||||
Backgrounds[3].Mode = BackgroundMode.Affine;
|
||||
break;
|
||||
case 3:
|
||||
Backgrounds[3].Mode = BackgroundMode.Extended;
|
||||
break;
|
||||
case 4:
|
||||
Backgrounds[2].Mode = BackgroundMode.Affine;
|
||||
Backgrounds[3].Mode = BackgroundMode.Extended;
|
||||
break;
|
||||
case 5:
|
||||
Backgrounds[2].Mode = BackgroundMode.Extended;
|
||||
Backgrounds[3].Mode = BackgroundMode.Extended;
|
||||
break;
|
||||
case 6:
|
||||
Backgrounds[0].Mode = BackgroundMode.Display3D;
|
||||
Backgrounds[2].Mode = BackgroundMode.Large;
|
||||
break;
|
||||
}
|
||||
switch (BgMode)
|
||||
{
|
||||
case 1:
|
||||
Backgrounds[2].Mode = BackgroundMode.Affine;
|
||||
break;
|
||||
case 2:
|
||||
Backgrounds[2].Mode = BackgroundMode.Affine;
|
||||
Backgrounds[3].Mode = BackgroundMode.Affine;
|
||||
break;
|
||||
}
|
||||
|
||||
// Extended mode backgrounds have extra options
|
||||
@ -620,154 +550,8 @@ namespace OptimeGBA
|
||||
}
|
||||
}
|
||||
|
||||
//[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||
//private void _RenderCharBackground(
|
||||
// uint vcount, byte* vram,
|
||||
// byte* palettes,
|
||||
// byte* winMasks,
|
||||
// uint* hi, uint* lo,
|
||||
// Background bg, bool mosaicX
|
||||
// )
|
||||
//{
|
||||
// uint charBase = bg.CharBaseBlock * CharBlockSize + CharBaseBlockCoarse * CoarseBlockSize;
|
||||
// uint mapBase = bg.MapBaseBlock * MapBlockSize + MapBaseBlockCoarse * CoarseBlockSize;
|
||||
|
||||
// uint pixelY = bg.VerticalOffset + vcount;
|
||||
// if (bg.EnableMosaic)
|
||||
// {
|
||||
// pixelY -= BgMosaicYCounter;
|
||||
// }
|
||||
// uint pixelYWrapped = pixelY & 255;
|
||||
|
||||
// uint screenSizeBase = bg.ScreenSize * 2;
|
||||
// uint verticalOffsetBlocks = CharBlockHeightTable[screenSizeBase + ((pixelY & 511) >> 8)];
|
||||
// uint mapVertOffset = MapBlockSize * verticalOffsetBlocks;
|
||||
|
||||
// uint tileY = pixelYWrapped >> 3;
|
||||
// uint intraTileY = pixelYWrapped & 7;
|
||||
|
||||
// uint pixelX = bg.HorizontalOffset;
|
||||
// uint intraTileX = bg.HorizontalOffset & 7;
|
||||
// uint lineIndex = 8 - intraTileX;
|
||||
|
||||
// uint tilesToRender = (uint)(Width / 8);
|
||||
// if (lineIndex < 8) tilesToRender++;
|
||||
|
||||
// uint mosaicXCounter = BgMosaicX;
|
||||
|
||||
// // Every byte of these vectors are filled
|
||||
// Vector256<int> metaVec = Vector256.Create((bg.Priority << 8) | (1 << bg.Id));
|
||||
|
||||
// for (uint tile = 0; tile < tilesToRender; tile++)
|
||||
// {
|
||||
// uint pixelXWrapped = pixelX & 255;
|
||||
|
||||
// // 2 bytes per tile
|
||||
// uint tileX = pixelXWrapped >> 3;
|
||||
// uint horizontalOffsetBlocks = CharBlockWidthTable[screenSizeBase + ((pixelX & 511) >> 8)];
|
||||
// uint mapHoriOffset = MapBlockSize * horizontalOffsetBlocks;
|
||||
// uint mapEntryIndex = mapBase + mapVertOffset + mapHoriOffset + tileY * 64 + tileX * 2;
|
||||
// uint mapEntry = GetUshort(vram, mapEntryIndex);
|
||||
|
||||
// uint tileNumber = mapEntry & 1023; // 10 bits
|
||||
// bool xFlip = BitTest(mapEntry, 10);
|
||||
// bool yFlip = BitTest(mapEntry, 11);
|
||||
|
||||
// uint effectiveIntraTileY = intraTileY;
|
||||
// if (yFlip)
|
||||
// {
|
||||
// effectiveIntraTileY ^= 7;
|
||||
// }
|
||||
|
||||
// Vector256<uint> clearMaskVec;
|
||||
// Vector256<uint> indicesVec = Vector256<uint>.Zero;
|
||||
// uint paletteRow = 0;
|
||||
|
||||
// if (bg.Use8BitColor)
|
||||
// {
|
||||
// clearMaskVec = Vector256.Create(0xFFU);
|
||||
|
||||
// uint vramTileAddr = charBase + tileNumber * 64 + effectiveIntraTileY * 8;
|
||||
// ulong data = GetUlong(vram, vramTileAddr);
|
||||
|
||||
// if (data != 0)
|
||||
// {
|
||||
// indicesVec = Avx2.ConvertToVector256Int32((byte*)&data).AsUInt32();
|
||||
// if (xFlip)
|
||||
// {
|
||||
// // First, reverse within 128-bit lanes
|
||||
// indicesVec = Avx2.Shuffle(indicesVec, 0b00_01_10_11);
|
||||
// // Then, swap upper and lower halves
|
||||
// indicesVec = Avx2.Permute2x128(indicesVec, indicesVec, 1);
|
||||
// }
|
||||
// indicesVec = Avx2.And(indicesVec, clearMaskVec);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// pixelX += 8;
|
||||
// lineIndex += 8;
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// clearMaskVec = Vector256.Create(0xFU);
|
||||
|
||||
// paletteRow = (mapEntry >> 12) & 0xF;
|
||||
// uint vramTileAddr = charBase + tileNumber * 32 + effectiveIntraTileY * 4;
|
||||
|
||||
// uint data = GetUint(vram, vramTileAddr);
|
||||
|
||||
// if (data != 0)
|
||||
// {
|
||||
// Vector256<uint> shifts;
|
||||
// if (xFlip)
|
||||
// {
|
||||
// shifts = Vector256.Create(28U, 24U, 20U, 16U, 12U, 8U, 4U, 0U);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// shifts = Vector256.Create(0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U);
|
||||
// }
|
||||
// indicesVec = Vector256.Create(data);
|
||||
// indicesVec = Avx2.ShiftRightLogicalVariable(indicesVec, shifts);
|
||||
// indicesVec = Avx2.And(indicesVec, clearMaskVec);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// pixelX += 8;
|
||||
// lineIndex += 8;
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Vector256<int> color = Avx2.GatherVector256((int*)((ushort*)palettes + paletteRow * 16), indicesVec.AsInt32(), sizeof(ushort));
|
||||
// color = Avx2.And(color, Vector256.Create(0xFFFF));
|
||||
// // Weave metadata (priority, ID) into color data
|
||||
// color = Avx2.Or(color, Avx2.ShiftLeftLogical(metaVec, 16));
|
||||
|
||||
// Vector256<int> winMask = Avx2.ConvertToVector256Int32((byte*)(winMasks + lineIndex));
|
||||
// winMask = Avx2.And(winMask, metaVec);
|
||||
// winMask = Avx2.CompareEqual(winMask, Vector256<int>.Zero);
|
||||
// // Get important color bits
|
||||
// Vector256<int> clear = Avx2.And(indicesVec, clearMaskVec).AsInt32();
|
||||
// // Are those bits clear?
|
||||
// clear = Avx2.CompareEqual(clear, Vector256<int>.Zero);
|
||||
// // Merge with window mask
|
||||
// winMask = Avx2.Or(winMask, clear);
|
||||
// winMask = Avx2.Xor(winMask, Vector256.Create(0xFFFFFFFF).AsInt32());
|
||||
|
||||
// // Push back covered pixels from hi to lo
|
||||
// Avx2.MaskStore((int*)(lo + lineIndex), winMask, Avx2.LoadVector256((int*)(hi + lineIndex)));
|
||||
// Avx2.MaskStore((int*)(hi + lineIndex), winMask, color);
|
||||
|
||||
// pixelX += 8;
|
||||
// lineIndex += 8;
|
||||
// }
|
||||
//}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void _RenderCharBackground(
|
||||
unsafe private void _RenderCharBackground(
|
||||
uint vcount, byte* vram,
|
||||
byte* palettes,
|
||||
byte* winMasks,
|
||||
@ -802,7 +586,7 @@ namespace OptimeGBA
|
||||
uint mosaicXCounter = BgMosaicX;
|
||||
|
||||
// Every byte of these vectors are filled
|
||||
Vector256<int> metaVec = Vector256<int>.Create((bg.Priority << 8) | (1 << bg.Id));
|
||||
v256 metaVec = new v256((int)((bg.Priority << 8) | (1 << bg.Id)));
|
||||
|
||||
for (uint tile = 0; tile < tilesToRender; tile++)
|
||||
{
|
||||
@ -825,29 +609,28 @@ namespace OptimeGBA
|
||||
effectiveIntraTileY ^= 7;
|
||||
}
|
||||
|
||||
Vector256<uint> clearMaskVec;
|
||||
Vector256<uint> indicesVec = Vector256<uint>.Zero;
|
||||
v256 clearMaskVec;
|
||||
v256 indicesVec;
|
||||
uint paletteRow = 0;
|
||||
|
||||
if (bg.Use8BitColor)
|
||||
{
|
||||
clearMaskVec = Vector256<uint>.Create(0xFFU);
|
||||
clearMaskVec = new v256(0xFFU);
|
||||
|
||||
uint vramTileAddr = charBase + tileNumber * 64 + effectiveIntraTileY * 8;
|
||||
ulong data = GetUlong(vram, vramTileAddr);
|
||||
|
||||
if (data != 0)
|
||||
{
|
||||
//indicesVec = Avx2.ConvertToVector256Int32((byte*)&data).AsUInt32();
|
||||
indicesVec = Avx2.ConvertToVector256Int32(data).AsUInt32();
|
||||
indicesVec = Avx2.mm256_cvtepu8_epi32(new v128(data));
|
||||
if (xFlip)
|
||||
{
|
||||
// First, reverse within 128-bit lanes
|
||||
indicesVec = Avx2.Shuffle(indicesVec, 0b00_01_10_11);
|
||||
indicesVec = Avx2.mm256_shuffle_epi32(indicesVec, 0b00_01_10_11);
|
||||
// Then, swap upper and lower halves
|
||||
indicesVec = Avx2.Permute2x128(indicesVec, indicesVec, 1);
|
||||
indicesVec = Avx2.mm256_permute2x128_si256(indicesVec, indicesVec, 1);
|
||||
}
|
||||
indicesVec = Avx2.And(indicesVec, clearMaskVec);
|
||||
indicesVec = Avx2.mm256_and_si256(indicesVec, clearMaskVec);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -858,7 +641,7 @@ namespace OptimeGBA
|
||||
}
|
||||
else
|
||||
{
|
||||
clearMaskVec = Vector256<uint>.Create(0xFU);
|
||||
clearMaskVec = new v256(0xFU);
|
||||
|
||||
paletteRow = (mapEntry >> 12) & 0xF;
|
||||
uint vramTileAddr = charBase + tileNumber * 32 + effectiveIntraTileY * 4;
|
||||
@ -867,18 +650,18 @@ namespace OptimeGBA
|
||||
|
||||
if (data != 0)
|
||||
{
|
||||
Vector256<uint> shifts;
|
||||
v256 shifts;
|
||||
if (xFlip)
|
||||
{
|
||||
shifts = Vector256<uint>.Create(28U, 24U, 20U, 16U, 12U, 8U, 4U, 0U);
|
||||
shifts = new v256(28U, 24U, 20U, 16U, 12U, 8U, 4U, 0U);
|
||||
}
|
||||
else
|
||||
{
|
||||
shifts = Vector256<uint>.Create(0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U);
|
||||
shifts = new v256(0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U);
|
||||
}
|
||||
indicesVec = Vector256<uint>.Create(data);
|
||||
indicesVec = Avx2.ShiftRightLogicalVariable(indicesVec, shifts);
|
||||
indicesVec = Avx2.And(indicesVec, clearMaskVec);
|
||||
indicesVec = new v256(data);
|
||||
indicesVec = Avx2.mm256_srlv_epi32(indicesVec, shifts);
|
||||
indicesVec = Avx2.mm256_and_si256(indicesVec, clearMaskVec);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -888,25 +671,28 @@ namespace OptimeGBA
|
||||
}
|
||||
}
|
||||
|
||||
Vector256<int> color = Avx2.GatherVector256((int*)((ushort*)palettes + paletteRow * 16), indicesVec.AsInt32(), sizeof(ushort));
|
||||
color = Avx2.And(color, Vector256<int>.Create(0xFFFF));
|
||||
v256 color = Avx2.mm256_i32gather_epi32((int*)((ushort*)palettes + paletteRow * 16), indicesVec, sizeof(ushort));
|
||||
color = Avx2.mm256_and_si256(color, new v256(0xFFFF));
|
||||
// Weave metadata (priority, ID) into color data
|
||||
color = Avx2.Or(color, Avx2.ShiftLeftLogical(metaVec, 16));
|
||||
color = Avx2.mm256_or_si256(color, Avx2.mm256_slli_epi32(metaVec, 16));
|
||||
|
||||
Vector256<int> winMask = Avx2.ConvertToVector256Int32((byte*)(winMasks + lineIndex));
|
||||
winMask = Avx2.And(winMask, metaVec);
|
||||
winMask = Avx2.CompareEqual(winMask, Vector256<int>.Zero);
|
||||
ulong addr = GetUlong(winMasks, lineIndex);
|
||||
v256 winMask = Avx2.mm256_cvtepi8_epi32(new v128(addr));
|
||||
winMask = Avx2.mm256_and_si256(winMask, metaVec);
|
||||
winMask = Avx2.mm256_cmpeq_epi32(winMask, new v256((byte)0));
|
||||
// Get important color bits
|
||||
Vector256<int> clear = Avx2.And(indicesVec, clearMaskVec).AsInt32();
|
||||
v256 clear = Avx2.mm256_and_si256(indicesVec, clearMaskVec);
|
||||
// Are those bits clear?
|
||||
clear = Avx2.CompareEqual(clear, Vector256<int>.Zero);
|
||||
clear = Avx2.mm256_cmpeq_epi32(clear, new v256(0));
|
||||
// Merge with window mask
|
||||
winMask = Avx2.Or(winMask, clear);
|
||||
winMask = Avx2.Xor(winMask, Vector256<int>.Create(0xFFFFFFFF).AsInt32());
|
||||
winMask = Avx2.mm256_or_si256(winMask, clear);
|
||||
winMask = Avx2.mm256_xor_si256(winMask, new v256(int.MinValue));
|
||||
|
||||
// Push back covered pixels from hi to lo
|
||||
Avx2.MaskStore((int*)(lo + lineIndex), winMask, Avx2.LoadVector256((int*)(hi + lineIndex)));
|
||||
Avx2.MaskStore((int*)(hi + lineIndex), winMask, color);
|
||||
// This render the front image
|
||||
Avx2.mm256_maskstore_epi32((void*)(lo + lineIndex), winMask, Avx2.mm256_stream_load_si256((void*)(hi + lineIndex)));
|
||||
// This render the background, has some bugs
|
||||
Avx2.mm256_maskstore_epi32((void*)(hi + lineIndex), winMask, color);
|
||||
|
||||
pixelX += 8;
|
||||
lineIndex += 8;
|
||||
@ -1178,7 +964,7 @@ namespace OptimeGBA
|
||||
uint tileX = (uint)(objX / 8);
|
||||
uint tileY = (uint)(objY / 8);
|
||||
|
||||
uint charBase = Nds != null ? 0U : 0x10000U;
|
||||
uint charBase = false ? 0U : 0x10000U;
|
||||
|
||||
tile <<= (int)TileObj1DBoundary;
|
||||
uint effectiveTileNumber = (uint)(tile + tileX);
|
||||
@ -1362,22 +1148,16 @@ namespace OptimeGBA
|
||||
|
||||
public bool BgIsEnabled(int id)
|
||||
{
|
||||
if (Nds == null)
|
||||
|
||||
switch (BgMode)
|
||||
{
|
||||
switch (BgMode)
|
||||
{
|
||||
case 1:
|
||||
if (id == 3) return false;
|
||||
break;
|
||||
case 2:
|
||||
if (id == 0) return false;
|
||||
if (id == 1) return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BgMode == 6 && (id == 0 || id == 2)) return false;
|
||||
case 1:
|
||||
if (id == 3) return false;
|
||||
break;
|
||||
case 2:
|
||||
if (id == 0) return false;
|
||||
if (id == 1) return false;
|
||||
break;
|
||||
}
|
||||
|
||||
return ScreenDisplayBg[id] && DebugEnableBg[id];
|
||||
@ -1403,25 +1183,12 @@ namespace OptimeGBA
|
||||
RenderAffineBitmapBackground(vcount, vram, bg, false);
|
||||
break;
|
||||
case BackgroundMode.Display3D:
|
||||
Render3DBackground(vcount, vram, bg);
|
||||
//Render3DBackground(vcount, vram, bg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Render3DBackground(uint vcount, byte* vram, Background bg)
|
||||
{
|
||||
uint srcBase = (uint)(vcount * Width);
|
||||
|
||||
ushort meta = bg.GetMeta();
|
||||
|
||||
for (uint i = 0; i < Width; i++)
|
||||
{
|
||||
if (Nds.Ppu3D.Screen[srcBase + i] != 0)
|
||||
PlaceBgPixel(i + 8, Nds.Ppu3D.Screen[srcBase + i], meta);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RenderAffineBitmapBackground(uint vcount, byte* vram, Background bg, bool fullColor)
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6510d22717f6584b81af5ea97b0569a
|
||||
guid: 53de3b275b2214443a61d7a3d8c35e3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public delegate void AudioCallback(short[] stereo16BitInterleavedData);
|
||||
public delegate void AudioCallback(float[] stereo16BitInterleavedData);
|
||||
|
||||
public abstract class Provider {
|
||||
public bool OutputAudio = true;
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73818cdc5ac0e0647a35b1b78a717b70
|
||||
guid: 3c36d17ca19732446911ae6dc3eee94e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -9,7 +9,7 @@ namespace OptimeGBA
|
||||
|
||||
public byte[] Bios;
|
||||
public byte[] Rom;
|
||||
|
||||
public string RomName;
|
||||
public string RomId;
|
||||
|
||||
public ProviderGba(byte[] bios, byte[] rom, string savPath, AudioCallback audioCallback)
|
||||
@ -18,6 +18,10 @@ namespace OptimeGBA
|
||||
Rom = rom;
|
||||
AudioCallback = audioCallback;
|
||||
SavPath = savPath;
|
||||
if (rom.Length > 0xA0 + 12)
|
||||
{
|
||||
RomName = Encoding.ASCII.GetString(Rom, 0xA0, 12);
|
||||
}
|
||||
|
||||
if (rom.Length >= 0xAC + 4)
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b60a85ffb5daaa4aa83e8999b108e9d
|
||||
guid: 05e3df20197e4ba46a0d39f32107f0a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,26 +0,0 @@
|
||||
using System.Text;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed class ProviderNds : Provider
|
||||
{
|
||||
public bool DirectBoot = true;
|
||||
|
||||
public byte[] Bios7;
|
||||
public byte[] Bios9;
|
||||
public byte[] Firmware;
|
||||
public byte[] Rom;
|
||||
|
||||
public string RomId;
|
||||
|
||||
public ProviderNds(byte[] bios7, byte[] bios9, byte[] firmware, byte[] rom, string savPath, AudioCallback audioCallback)
|
||||
{
|
||||
Bios7 = bios7;
|
||||
Bios9 = bios9;
|
||||
Firmware = firmware;
|
||||
Rom = rom;
|
||||
AudioCallback = audioCallback;
|
||||
SavPath = savPath;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f149b75fc4a1c4b4881e9d79fc7a1edc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
72
Assets/emulator/RingBuffer.cs
Normal file
72
Assets/emulator/RingBuffer.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System.Threading;
|
||||
|
||||
public class RingBuffer<T>
|
||||
{
|
||||
private readonly T[] buffer;
|
||||
private readonly int capacity;
|
||||
private int writePos;
|
||||
private int readPos;
|
||||
private int count;
|
||||
|
||||
public RingBuffer(int capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
this.buffer = new T[capacity];
|
||||
this.writePos = 0;
|
||||
this.readPos = 0;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
public void Write(T item)
|
||||
{
|
||||
int localWritePos;
|
||||
int localReadPos;
|
||||
|
||||
do
|
||||
{
|
||||
localWritePos = Volatile.Read(ref writePos);
|
||||
localReadPos = Volatile.Read(ref readPos);
|
||||
|
||||
int nextWritePos = (localWritePos + 1) % capacity;
|
||||
|
||||
if (nextWritePos == localReadPos)
|
||||
{
|
||||
// 缓冲区已满,覆盖最旧的未读数据
|
||||
Interlocked.CompareExchange(ref readPos, (localReadPos + 1) % capacity, localReadPos);
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref writePos, (localWritePos + 1) % capacity, localWritePos) != localWritePos);
|
||||
|
||||
buffer[localWritePos] = item;
|
||||
Interlocked.Increment(ref count);
|
||||
}
|
||||
|
||||
public bool TryRead(out T item)
|
||||
{
|
||||
item = default(T);
|
||||
|
||||
int localReadPos;
|
||||
int localWritePos;
|
||||
|
||||
do
|
||||
{
|
||||
localReadPos = Volatile.Read(ref readPos);
|
||||
localWritePos = Volatile.Read(ref writePos);
|
||||
|
||||
if (localReadPos == localWritePos)
|
||||
{
|
||||
return false; // 缓冲区为空
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref readPos, (localReadPos + 1) % capacity, localReadPos) != localReadPos);
|
||||
|
||||
item = buffer[localReadPos];
|
||||
Interlocked.Decrement(ref count);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int Available()
|
||||
{
|
||||
return Volatile.Read(ref count);
|
||||
}
|
||||
}
|
11
Assets/emulator/RingBuffer.cs.meta
Normal file
11
Assets/emulator/RingBuffer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4889053f721ce7f4283fae5176d60772
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,162 +0,0 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
|
||||
public enum RtcNdsState
|
||||
{
|
||||
ReceivingCommand,
|
||||
CommandEntered
|
||||
}
|
||||
|
||||
public class RtcNds
|
||||
{
|
||||
public void UpdateTime()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
DateAndTime[0] = ConvertToBcd((byte)(now.Year % 100));
|
||||
DateAndTime[1] = ConvertToBcd((byte)now.Month);
|
||||
DateAndTime[2] = ConvertToBcd((byte)now.Day);
|
||||
DateAndTime[3] = ConvertToBcd((byte)now.DayOfWeek);
|
||||
DateAndTime[4] = ConvertToBcd((byte)now.Hour);
|
||||
DateAndTime[5] = ConvertToBcd((byte)now.Minute);
|
||||
DateAndTime[6] = ConvertToBcd((byte)now.Second);
|
||||
}
|
||||
|
||||
public static byte ConvertToBcd(byte val)
|
||||
{
|
||||
uint upper = val / 10U;
|
||||
uint lower = val % 10U;
|
||||
|
||||
return (byte)((upper << 4) | lower);
|
||||
}
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000138:
|
||||
return Rtc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte Rtc;
|
||||
byte Command;
|
||||
int BitsWritten;
|
||||
byte Status1;
|
||||
|
||||
byte[] DateAndTime = new byte[7];
|
||||
|
||||
RtcNdsState State;
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000138:
|
||||
if (BitTest(val, 2)) // CS
|
||||
{
|
||||
if (BitTest(Rtc, 1) && !BitTest(val, 1)) // /SC to low
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case RtcNdsState.ReceivingCommand:
|
||||
Command |= (byte)((val & 1) << (7 - BitsWritten));
|
||||
if (++BitsWritten == 8)
|
||||
{
|
||||
State = RtcNdsState.CommandEntered;
|
||||
// Console.WriteLine("RTC: command set " + Util.Hex(Command, 2));
|
||||
BitsWritten = 0;
|
||||
|
||||
switch ((Command >> 1) & 0b111)
|
||||
{
|
||||
case 0:
|
||||
// Console.WriteLine("RTC status 1");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Console.WriteLine("RTC status 2");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Console.WriteLine("RTC date and time");
|
||||
UpdateTime();
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// Console.WriteLine("RTC time");
|
||||
UpdateTime();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RtcNdsState.CommandEntered:
|
||||
if (!BitTest(val, 4)) // Read
|
||||
{
|
||||
val &= 0xFE; // Erase bit 0
|
||||
int commandBits = (Command >> 1) & 0b111;
|
||||
|
||||
int byteNum = BitsWritten / 8;
|
||||
int bitNum = (BitsWritten % 8);
|
||||
switch (commandBits)
|
||||
{
|
||||
case 0: // Status 1
|
||||
// Console.WriteLine("status 1 read");
|
||||
val |= (byte)((Status1 >> BitsWritten) & 1);
|
||||
break;
|
||||
|
||||
case 2: // Date & Time (7 bytes)
|
||||
val |= (byte)((DateAndTime[byteNum] >> bitNum) & 1);
|
||||
break;
|
||||
|
||||
case 3: // Time (3 bytes);
|
||||
val |= (byte)((DateAndTime[byteNum + 4] >> bitNum) & 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Console.WriteLine("RTC: unknown command read " + commandBits);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byte bit = (byte)(val & 1U);
|
||||
switch ((Command >> 1) & 0b111)
|
||||
{
|
||||
case 0: // Status 1
|
||||
if (BitsWritten >= 1 && BitsWritten <= 3)
|
||||
{
|
||||
// Console.WriteLine("status 1 write ");
|
||||
Status1 &= (byte)(~(1 << BitsWritten));
|
||||
Status1 |= (byte)(bit << BitsWritten);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
BitsWritten++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!BitTest(Rtc, 1) && BitTest(val, 1) && !BitTest(val, 4))
|
||||
{
|
||||
val &= 0xFE;
|
||||
val |= (byte)(Rtc & 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Command = 0;
|
||||
BitsWritten = 0;
|
||||
State = RtcNdsState.ReceivingCommand;
|
||||
}
|
||||
|
||||
Rtc = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f19637330f5677d4fa311abfed7f81f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a3ce826f3cf2804588653e63ff30378
|
||||
guid: 56299f29f00a78c4e84616fb9fc75b69
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,176 +0,0 @@
|
||||
using System;
|
||||
//using NAudio.Dsp;
|
||||
using static OptimeGBA.CoreUtil;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
|
||||
public class SoundgoodizerFilterChannel
|
||||
{
|
||||
// Each biquad filter has a slope of 12db/oct so 2 biquads chained gets us 24db/oct
|
||||
//BiQuadFilter[] LowFilters = new BiQuadFilter[2];
|
||||
//BiQuadFilter[] MidFilters = new BiQuadFilter[4];
|
||||
//BiQuadFilter[] HighFilters = new BiQuadFilter[2];
|
||||
|
||||
public float OutLow = 0;
|
||||
public float OutMid = 0;
|
||||
public float OutHigh = 0;
|
||||
|
||||
public bool DbPerOct24;
|
||||
|
||||
public SoundgoodizerFilterChannel(bool dbPerOct24, float sampleRate, float lowHz, float highHz)
|
||||
{
|
||||
DbPerOct24 = dbPerOct24;
|
||||
|
||||
// q = 1/sqrt(2) maximally flat "butterworth" filter
|
||||
float q = 1F/(float)Math.Sqrt(2);
|
||||
//LowFilters[0] = BiQuadFilter.LowPassFilter(sampleRate, lowHz, q);
|
||||
//LowFilters[1] = BiQuadFilter.LowPassFilter(sampleRate, lowHz, q);
|
||||
|
||||
//MidFilters[0] = BiQuadFilter.HighPassFilter(sampleRate, lowHz, q);
|
||||
//MidFilters[1] = BiQuadFilter.LowPassFilter(sampleRate, highHz, q);
|
||||
//MidFilters[2] = BiQuadFilter.HighPassFilter(sampleRate, lowHz, q);
|
||||
//MidFilters[3] = BiQuadFilter.LowPassFilter(sampleRate, highHz, q);
|
||||
|
||||
//HighFilters[0] = BiQuadFilter.HighPassFilter(sampleRate, highHz, q);
|
||||
//HighFilters[1] = BiQuadFilter.HighPassFilter(sampleRate, highHz, q);
|
||||
}
|
||||
|
||||
public void ChangeFilterParams(bool dbPerOct24, float sampleRate, float lowHz, float highHz)
|
||||
{
|
||||
DbPerOct24 = dbPerOct24;
|
||||
|
||||
float q = 1F/(float)Math.Sqrt(2);
|
||||
//LowFilters[0].SetLowPassFilter(sampleRate, lowHz, q);
|
||||
//LowFilters[1].SetLowPassFilter(sampleRate, lowHz, q);
|
||||
|
||||
//MidFilters[0].SetHighPassFilter(sampleRate, lowHz, q);
|
||||
//MidFilters[1].SetLowPassFilter(sampleRate, highHz, q);
|
||||
//MidFilters[2].SetHighPassFilter(sampleRate, lowHz, q);
|
||||
//MidFilters[3].SetLowPassFilter(sampleRate, highHz, q);
|
||||
|
||||
//HighFilters[0].SetHighPassFilter(sampleRate, highHz, q);
|
||||
//HighFilters[1].SetHighPassFilter(sampleRate, highHz, q);
|
||||
}
|
||||
|
||||
public void Process(float inVal)
|
||||
{
|
||||
OutLow = inVal;
|
||||
OutMid = inVal;
|
||||
OutHigh = inVal;
|
||||
|
||||
//for (int i = 0; i < (DbPerOct24 ? 2 : 1); i++)
|
||||
//{
|
||||
// OutLow = LowFilters[i].Transform(OutLow);
|
||||
//}
|
||||
|
||||
//for (int i = 0; i < (DbPerOct24 ? 4 : 2); i++)
|
||||
//{
|
||||
// OutMid = MidFilters[i].Transform(OutMid);
|
||||
//}
|
||||
|
||||
//for (int i = 0; i < (DbPerOct24 ? 2 : 1); i++)
|
||||
//{
|
||||
// OutHigh = HighFilters[i].Transform(OutHigh);
|
||||
//}
|
||||
}
|
||||
}
|
||||
public class Soundgoodizer
|
||||
{
|
||||
public float MixLevel = 0.6F;
|
||||
|
||||
public SoundgoodizerFilterChannel L;
|
||||
public SoundgoodizerFilterChannel R;
|
||||
|
||||
public float OutL = 0;
|
||||
public float OutR = 0;
|
||||
|
||||
//SimpleCompressor CompressorLow;
|
||||
//SimpleCompressor CompressorMid;
|
||||
//SimpleCompressor CompressorHigh;
|
||||
//SimpleCompressor CompressorMaster;
|
||||
|
||||
public float PreGainLow = 1.78F;
|
||||
public float PreGainMid = 2.09F;
|
||||
public float PreGainHigh = 2.20F;
|
||||
public float PreGainMaster = 1;
|
||||
|
||||
public float PostGainLow = 1.91F;
|
||||
public float PostGainMid = 1.00F;
|
||||
public float PostGainHigh = 1.40F;
|
||||
|
||||
public bool DbPerOct24;
|
||||
public float SampleRate;
|
||||
public float LowHz;
|
||||
public float HighHz;
|
||||
|
||||
// Default filter cutoffs based on Soundgoodizer Preset A from FL Studio
|
||||
public Soundgoodizer(float sampleRate) : this(true, sampleRate, 200, 3000) { }
|
||||
|
||||
public Soundgoodizer(bool dbPerOct24, float sampleRate, float lowHz, float highHz)
|
||||
{
|
||||
DbPerOct24 = dbPerOct24;
|
||||
SampleRate = sampleRate;
|
||||
LowHz = lowHz;
|
||||
HighHz = highHz;
|
||||
|
||||
L = new SoundgoodizerFilterChannel(dbPerOct24, sampleRate, lowHz, highHz);
|
||||
R = new SoundgoodizerFilterChannel(dbPerOct24, sampleRate, lowHz, highHz);
|
||||
|
||||
// Compressor parameters also taken from Soundgoodizer Preset A
|
||||
//CompressorLow = new SimpleCompressor(2.0, 137.48, sampleRate);
|
||||
//CompressorMid = new SimpleCompressor(2.0, 85.53, sampleRate);
|
||||
//CompressorHigh = new SimpleCompressor(2.0, 85.53, sampleRate);
|
||||
//CompressorMaster = new SimpleCompressor(2.0, 85.53, sampleRate);
|
||||
}
|
||||
|
||||
public void ChangeFilterParams(bool dbPerOct24, float sampleRate, float lowHz, float highHz)
|
||||
{
|
||||
DbPerOct24 = dbPerOct24;
|
||||
SampleRate = sampleRate;
|
||||
LowHz = lowHz;
|
||||
HighHz = highHz;
|
||||
|
||||
if (lowHz > highHz) Swap(ref highHz, ref lowHz);
|
||||
L.ChangeFilterParams(dbPerOct24, sampleRate, lowHz, highHz);
|
||||
R.ChangeFilterParams(dbPerOct24, sampleRate, lowHz, highHz);
|
||||
}
|
||||
|
||||
public void Process(float inL, float inR)
|
||||
{
|
||||
L.Process(inL);
|
||||
R.Process(inR);
|
||||
|
||||
// Apply pre-gain (Soundgoodizer Preset A)
|
||||
double outLowL = L.OutLow * PreGainLow;
|
||||
double outLowR = R.OutLow * PreGainLow;
|
||||
double outMidL = L.OutMid * PreGainMid;
|
||||
double outMidR = R.OutMid * PreGainMid;
|
||||
double outHighL = L.OutHigh * PreGainHigh;
|
||||
double outHighR = R.OutHigh * PreGainHigh;
|
||||
|
||||
//CompressorLow.Process(ref outLowL, ref outLowR);
|
||||
//CompressorMid.Process(ref outMidL, ref outMidR);
|
||||
//CompressorHigh.Process(ref outHighL, ref outHighR);
|
||||
|
||||
// Apply post-gain (Soundgoodizer Preset A)
|
||||
outLowL *= PostGainLow;
|
||||
outLowR *= PostGainLow;
|
||||
outMidL *= PostGainMid;
|
||||
outMidR *= PostGainMid;
|
||||
outHighL *= PostGainHigh;
|
||||
outHighR *= PostGainHigh;
|
||||
|
||||
double outL = outLowL + outMidL + outHighL;
|
||||
double outR = outLowR + outMidR + outHighR;
|
||||
|
||||
outL *= PreGainMaster;
|
||||
outR *= PreGainMaster;
|
||||
|
||||
//CompressorMaster.Process(ref outL, ref outR);
|
||||
|
||||
OutL = (float)(MixLevel * outL + (1 - MixLevel) * inL);
|
||||
OutR = (float)(MixLevel * outR + (1 - MixLevel) * inR);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4244c1f833ecf894b8ad5d41c8277cda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,183 +0,0 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum SpiDevice : byte
|
||||
{
|
||||
PowerManager = 0,
|
||||
Firmware = 1,
|
||||
Touchscreen = 2
|
||||
}
|
||||
|
||||
public enum SpiTouchscreenState
|
||||
{
|
||||
Ready,
|
||||
Command,
|
||||
}
|
||||
|
||||
public unsafe sealed class Spi
|
||||
{
|
||||
public Nds Nds;
|
||||
|
||||
public Spi(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
|
||||
Flash = new SpiFlash(Nds.Provider.Firmware);
|
||||
}
|
||||
|
||||
// From Nocash's original DS
|
||||
byte[] Id = new byte[] { 0x20, 0x40, 0x12 };
|
||||
|
||||
// SPICNT
|
||||
byte BaudRate;
|
||||
SpiDevice DeviceSelect;
|
||||
bool TransferSize;
|
||||
bool ChipSelHold;
|
||||
bool EnableIrq;
|
||||
bool EnableSpi;
|
||||
bool Busy;
|
||||
|
||||
// Flash
|
||||
public SpiFlash Flash;
|
||||
|
||||
// Touchscreen state
|
||||
public SpiTouchscreenState TouchscreenState;
|
||||
public byte TouchscreenCommand;
|
||||
public byte TouchscreenDataByte;
|
||||
|
||||
public ushort TouchAdcX;
|
||||
public ushort TouchAdcY;
|
||||
|
||||
public void SetTouchPos(uint x, uint y)
|
||||
{
|
||||
ushort adcX1 = GetUshort(Flash.Data, 0x3FF58);
|
||||
ushort adcY1 = GetUshort(Flash.Data, 0x3FF5A);
|
||||
byte scrX1 = Flash.Data[0x3FF5C];
|
||||
byte scrY1 = Flash.Data[0x3FF5D];
|
||||
ushort adcX2 = GetUshort(Flash.Data, 0x3FF5E);
|
||||
ushort adcY2 = GetUshort(Flash.Data, 0x3FF60);
|
||||
byte scrX2 = Flash.Data[0x3FF62];
|
||||
byte scrY2 = Flash.Data[0x3FF63];
|
||||
|
||||
// Convert screen coords to calibrated ADC touchscreen coords
|
||||
TouchAdcX = (ushort)((x - (scrX1 - 1)) * (adcX2 - adcX1) / (scrX2 - scrX1) + adcX1);
|
||||
TouchAdcY = (ushort)((y - (scrY1 - 1)) * (adcY2 - adcY1) / (scrY2 - scrY1) + adcY1);
|
||||
}
|
||||
|
||||
public void ClearTouchPos()
|
||||
{
|
||||
TouchAdcX = 0;
|
||||
TouchAdcY = 0xFFF;
|
||||
}
|
||||
|
||||
public byte OutData;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x40001C0: // SPICNT B0
|
||||
val |= BaudRate;
|
||||
if (Busy) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x40001C1: // SPICNT B1
|
||||
val |= (byte)DeviceSelect;
|
||||
if (TransferSize) val = BitSet(val, 2);
|
||||
if (ChipSelHold) val = BitSet(val, 3);
|
||||
if (EnableIrq) val = BitSet(val, 6);
|
||||
if (EnableSpi) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x40001C2: // SPIDATA
|
||||
// Console.WriteLine("SPI: Read! " + Hex(InData, 2));
|
||||
if (!EnableSpi) return 0;
|
||||
return OutData;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x40001C0: // SPICNT B0
|
||||
BaudRate = (byte)(val & 0b11);
|
||||
break;
|
||||
case 0x40001C1: // SPICNT B1
|
||||
DeviceSelect = (SpiDevice)(val & 0b11);
|
||||
TransferSize = BitTest(val, 2);
|
||||
bool oldChipSelHold = ChipSelHold;
|
||||
ChipSelHold = BitTest(val, 3);
|
||||
EnableIrq = BitTest(val, 6);
|
||||
EnableSpi = BitTest(val, 7);
|
||||
|
||||
if (!EnableSpi)
|
||||
{
|
||||
ChipSelHold = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x40001C2: // SPIDATA
|
||||
TransferTo(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void TransferTo(byte val)
|
||||
{
|
||||
if (EnableSpi)
|
||||
{
|
||||
switch (DeviceSelect)
|
||||
{
|
||||
case SpiDevice.Firmware:
|
||||
OutData = Flash.TransferTo(val, TransferSize);
|
||||
break;
|
||||
case SpiDevice.Touchscreen:
|
||||
TransferToTouchscreen(val);
|
||||
break;
|
||||
case SpiDevice.PowerManager:
|
||||
// Console.WriteLine("Power manager access");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!ChipSelHold)
|
||||
{
|
||||
Flash.Deselect();
|
||||
TouchscreenState = SpiTouchscreenState.Ready;
|
||||
}
|
||||
}
|
||||
|
||||
public void TransferToTouchscreen(byte val)
|
||||
{
|
||||
switch (TouchscreenState)
|
||||
{
|
||||
case SpiTouchscreenState.Ready:
|
||||
TouchscreenState = SpiTouchscreenState.Command;
|
||||
OutData = 0;
|
||||
TouchscreenCommand = val;
|
||||
TouchscreenDataByte = 0;
|
||||
break;
|
||||
case SpiTouchscreenState.Command:
|
||||
switch ((TouchscreenCommand >> 4) & 0b111)
|
||||
{
|
||||
case 1: // Y position
|
||||
// Shift 12-byte up left three to get start with 1-bit dummy
|
||||
OutData = (byte)((TouchAdcY << 3) >> (8 * (1 - (TouchscreenDataByte & 1))));
|
||||
break;
|
||||
case 5: // X position
|
||||
OutData = (byte)((TouchAdcX << 3) >> (8 * (1 - (TouchscreenDataByte & 1))));
|
||||
// Console.WriteLine("Y");
|
||||
break;
|
||||
}
|
||||
TouchscreenDataByte++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc3ada11b34fabd45b3e4e141afd9b7b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,110 +0,0 @@
|
||||
using System;
|
||||
using static Util;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum SpiFlashState
|
||||
{
|
||||
Ready,
|
||||
Identification,
|
||||
ReceiveAddress,
|
||||
Reading,
|
||||
Status,
|
||||
TakePrefix, // For cartridges with IR and Flash
|
||||
}
|
||||
|
||||
public unsafe sealed class SpiFlash
|
||||
{
|
||||
public byte[] Data;
|
||||
|
||||
public SpiFlash(byte[] data) {
|
||||
Data = data;
|
||||
}
|
||||
|
||||
// Firmware flash state
|
||||
public SpiFlashState FlashState;
|
||||
public bool EnableWrite;
|
||||
public byte IdIndex;
|
||||
public uint Address;
|
||||
public byte AddressByteNum = 0;
|
||||
|
||||
// From Nocash's original DS
|
||||
byte[] Id = new byte[] { 0x20, 0x40, 0x12 };
|
||||
|
||||
public byte OutData;
|
||||
|
||||
public byte TransferTo(byte val, bool transferSize)
|
||||
{
|
||||
switch (FlashState)
|
||||
{
|
||||
case SpiFlashState.Ready:
|
||||
// Console.WriteLine("SPI: Receive command! " + Hex(val, 2));
|
||||
OutData = 0x00;
|
||||
switch (val)
|
||||
{
|
||||
case 0x06:
|
||||
EnableWrite = true;
|
||||
break;
|
||||
case 0x04:
|
||||
EnableWrite = false;
|
||||
break;
|
||||
case 0x9F:
|
||||
FlashState = SpiFlashState.Identification;
|
||||
IdIndex = 0;
|
||||
break;
|
||||
case 0x03:
|
||||
FlashState = SpiFlashState.ReceiveAddress;
|
||||
Address = 0;
|
||||
AddressByteNum = 0;
|
||||
break;
|
||||
case 0x05: // Identification
|
||||
// Console.WriteLine("SPI ID");
|
||||
OutData = 0x00;
|
||||
break;
|
||||
case 0x00:
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("SPI: Unimplemented command: " + Hex(val, 2));
|
||||
}
|
||||
break;
|
||||
case SpiFlashState.ReceiveAddress:
|
||||
// Console.WriteLine("SPI: Address byte write: " + Hex(val, 2));
|
||||
Address |= (uint)(val << ((2 - AddressByteNum) * 8));
|
||||
AddressByteNum++;
|
||||
if (AddressByteNum > 2)
|
||||
{
|
||||
AddressByteNum = 0;
|
||||
FlashState = SpiFlashState.Reading;
|
||||
// Console.WriteLine("SPI: Address written: " + Hex(Address, 6));
|
||||
}
|
||||
break;
|
||||
case SpiFlashState.Reading:
|
||||
// Console.WriteLine("SPI: Read from address: " + Hex(Address, 6));
|
||||
// Nds7.Cpu.Error("SPI");
|
||||
if (Address < 0x40000)
|
||||
{
|
||||
OutData = Data[Address];
|
||||
}
|
||||
else
|
||||
{
|
||||
OutData = 0;
|
||||
}
|
||||
Address += transferSize ? 2U : 1U;
|
||||
Address &= 0xFFFFFF;
|
||||
break;
|
||||
case SpiFlashState.Identification:
|
||||
OutData = Id[IdIndex];
|
||||
IdIndex++;
|
||||
IdIndex %= 3;
|
||||
break;
|
||||
}
|
||||
|
||||
return OutData;
|
||||
}
|
||||
|
||||
public void Deselect()
|
||||
{
|
||||
FlashState = SpiFlashState.Ready;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f7e39ea1a758aa4890a88f1d00e7703
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -68,6 +68,7 @@ namespace OptimeGBA
|
||||
ReloadVal &= 0x00FF;
|
||||
ReloadVal |= ((uint)val << 8);
|
||||
RecalculateInterval();
|
||||
|
||||
break;
|
||||
case 0x02: // TMCNT_H B0
|
||||
PrescalerSel = (uint)(val & 0b11);
|
||||
@ -103,7 +104,7 @@ namespace OptimeGBA
|
||||
Reload();
|
||||
Timers.Scheduler.AddEventRelative(GetSchedulerId(), CalculateOverflowCycles(), TimerOverflow);
|
||||
EnableCycles = CalculateAlignedCurrentTicks();
|
||||
// Console.WriteLine($"[Timer] {Id} Enable");
|
||||
// Debug.Log($"[Timer] {Id} Enable");
|
||||
}
|
||||
|
||||
Enabled = true;
|
||||
@ -212,7 +213,7 @@ namespace OptimeGBA
|
||||
}
|
||||
|
||||
EnableCycles = CalculateAlignedCurrentTicks() - cyclesLate;
|
||||
// Console.WriteLine($"[Timer] {Id} Overflow");
|
||||
// Debug.Log($"[Timer] {Id} Overflow");
|
||||
}
|
||||
|
||||
public void UnscheduledTimerIncrement()
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 298e97a62e33e8c428f0dd465f1382f9
|
||||
guid: 78841f13936f80a44b825d87e775adea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -11,18 +11,18 @@ static class Util
|
||||
|
||||
public static void WriteDebug(string text)
|
||||
{
|
||||
// Console.WriteLine(text);
|
||||
// Debug.Log(text);
|
||||
}
|
||||
|
||||
public static string Pad(string n, int width, char padChar)
|
||||
{
|
||||
return n.Length >= width ? n : string.Join(padChar, new int[width - (n.Length + 1)]) + n;
|
||||
}
|
||||
//public static string Pad(string n, int width, char padChar)
|
||||
//{
|
||||
// return n.Length >= width ? n : string.Join(padChar, new int[width - (n.Length + 1)]) + n;
|
||||
//}
|
||||
|
||||
public static string RightPad(string n, int width, char z)
|
||||
{
|
||||
return n.Length >= width ? n : n + string.Join(z, new int[width - (n.Length + 1)]);
|
||||
}
|
||||
//public static string RightPad(string n, int width, char z)
|
||||
//{
|
||||
// return n.Length >= width ? n : n + string.Join(z, new int[width - (n.Length + 1)]);
|
||||
//}
|
||||
|
||||
public static string Hex(long i, int digits)
|
||||
{
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a5554b2d514cfb40a6352bbea330fe6
|
||||
guid: 98d3717b73bea1b4b9b81c2493c8c47f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
90
Assets/emulator/VideoProvider.cs
Normal file
90
Assets/emulator/VideoProvider.cs
Normal file
@ -0,0 +1,90 @@
|
||||
using OptimeGBA;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
public class VideoProvider : MonoBehaviour
|
||||
{
|
||||
public RawImage m_drawCanvas;
|
||||
private RectTransform m_drawCanvasrect;
|
||||
private IntPtr wrapTexBufferPointer;
|
||||
private Texture2D wrapTex;
|
||||
private int TexBufferSize;
|
||||
|
||||
|
||||
uint[] wrapTexBuffer = new uint[240 * 160];
|
||||
Color32[] DisplayColorBuffer = new Color32[240 * 160];
|
||||
|
||||
|
||||
public void OnRenderFrame()
|
||||
{
|
||||
if (wrapTex == null)
|
||||
{
|
||||
wrapTex = new Texture2D(240, 160, TextureFormat.RGBA32, false);
|
||||
wrapTex.filterMode = FilterMode.Point;
|
||||
//wrapTexBuffer = screenData;
|
||||
|
||||
// 固定数组,防止垃圾回收器移动它
|
||||
GCHandle handle = GCHandle.Alloc(wrapTexBuffer, GCHandleType.Pinned);
|
||||
// 获取数组的指针
|
||||
wrapTexBufferPointer = handle.AddrOfPinnedObject();
|
||||
m_drawCanvas.texture = wrapTex;
|
||||
TexBufferSize = wrapTexBuffer.Length * 4;
|
||||
|
||||
|
||||
m_drawCanvasrect = m_drawCanvas.GetComponent<RectTransform>();
|
||||
float targetWidth = ((float)240 / 160) * m_drawCanvasrect.rect.height;
|
||||
m_drawCanvasrect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, targetWidth);
|
||||
|
||||
}
|
||||
|
||||
DrawDisplay();
|
||||
}
|
||||
public void DrawDisplay()
|
||||
{
|
||||
var buf = Emulator.instance.ShowBackBuf ? Emulator.instance.gba.Ppu.Renderer.ScreenBack : Emulator.instance.gba.Ppu.Renderer.ScreenFront;
|
||||
unsafe
|
||||
{
|
||||
for (uint i = 0; i < 240 * 160; i++)
|
||||
{
|
||||
wrapTexBuffer[i] = PpuRenderer.ColorLutCorrected[buf[i] & 0x7FFF];
|
||||
fixed (uint* p = &wrapTexBuffer[i])
|
||||
{
|
||||
byte* bp = (byte*)p;
|
||||
DisplayColorBuffer[i] = new Color32(*(bp++), *(bp++), *(bp++), *(bp++));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//wrapTex.LoadRawTextureData(wrapTexBufferPointer, TexBufferSize);
|
||||
wrapTex.SetPixels32(DisplayColorBuffer, 0);
|
||||
wrapTex.Apply();
|
||||
|
||||
}
|
||||
|
||||
//public void SetDrawData(uint[] screenData, byte[] lineColorMode, int screenWidth, int screenHeight)
|
||||
//{
|
||||
// if (wrapTex == null)
|
||||
// {
|
||||
// //wrapTex = new Texture2D(272, 240, TextureFormat.BGRA32, false);
|
||||
// wrapTex = new Texture2D(272, 240, TextureFormat.RGBA32, false);
|
||||
// wrapTex.filterMode = FilterMode.Point;
|
||||
// wrapTexBuffer = screenData;
|
||||
|
||||
// // 固定数组,防止垃圾回收器移动它
|
||||
// GCHandle handle = GCHandle.Alloc(wrapTexBuffer, GCHandleType.Pinned);
|
||||
// // 获取数组的指针
|
||||
// wrapTexBufferPointer = handle.AddrOfPinnedObject();
|
||||
|
||||
// Image.texture = wrapTex;
|
||||
// Image.material.SetTexture("_MainTex", wrapTex);
|
||||
|
||||
// TexBufferSize = wrapTexBuffer.Length * 4;
|
||||
// }
|
||||
|
||||
// wrapTex.LoadRawTextureData(wrapTexBufferPointer, TexBufferSize);
|
||||
// wrapTex.Apply();
|
||||
//}
|
||||
}
|
11
Assets/emulator/VideoProvider.cs.meta
Normal file
11
Assets/emulator/VideoProvider.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 331cd2e4e0a49874cab11aa18b69e827
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,106 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class WavWriter {
|
||||
public const int BitsPerSample = 16;
|
||||
public const int Channels = 2;
|
||||
public int SampleRate;
|
||||
|
||||
public int RecordBufferAt;
|
||||
|
||||
public List<short> RecordBuffer = new List<short>();
|
||||
|
||||
public WavWriter(int sampleRate) {
|
||||
SampleRate = sampleRate;
|
||||
}
|
||||
|
||||
public void AddSample(short valL, short valR) {
|
||||
RecordBuffer.Add(valL);
|
||||
RecordBuffer.Add(valR);
|
||||
RecordBufferAt += 2;
|
||||
}
|
||||
|
||||
public void Save(string path) {
|
||||
var file = File.OpenWrite(path);
|
||||
|
||||
// RIFF header
|
||||
file.WriteByte(0x52);
|
||||
file.WriteByte(0x49);
|
||||
file.WriteByte(0x46);
|
||||
file.WriteByte(0x46);
|
||||
|
||||
int size = RecordBuffer.Count * Channels * (BitsPerSample / 2) - 8 + 44;
|
||||
file.WriteByte((byte)(size >> 0));
|
||||
file.WriteByte((byte)(size >> 8));
|
||||
file.WriteByte((byte)(size >> 16));
|
||||
file.WriteByte((byte)(size >> 24));
|
||||
|
||||
// WAVE
|
||||
file.WriteByte(0x57);
|
||||
file.WriteByte(0x41);
|
||||
file.WriteByte(0x56);
|
||||
file.WriteByte(0x45);
|
||||
|
||||
// Subchunk1ID "fmt "
|
||||
file.WriteByte(0x66);
|
||||
file.WriteByte(0x6d);
|
||||
file.WriteByte(0x74);
|
||||
file.WriteByte(0x20);
|
||||
|
||||
// Subchunk1Size
|
||||
file.WriteByte(16);
|
||||
file.WriteByte(0);
|
||||
file.WriteByte(0);
|
||||
file.WriteByte(0);
|
||||
|
||||
// AudioFormat
|
||||
file.WriteByte(1);
|
||||
file.WriteByte(0);
|
||||
|
||||
// 2 channels
|
||||
file.WriteByte(Channels);
|
||||
file.WriteByte(0);
|
||||
|
||||
// Sample rate
|
||||
file.WriteByte((byte)(SampleRate >> 0));
|
||||
file.WriteByte((byte)(SampleRate >> 8));
|
||||
file.WriteByte((byte)(SampleRate >> 16));
|
||||
file.WriteByte((byte)(SampleRate >> 24));
|
||||
|
||||
// ByteRate
|
||||
// SampleRate * NumChannels * BitsPerSample/8
|
||||
int byteRate = SampleRate * Channels * (BitsPerSample / 8);
|
||||
file.WriteByte((byte)(byteRate >> 0));
|
||||
file.WriteByte((byte)(byteRate >> 8));
|
||||
file.WriteByte((byte)(byteRate >> 16));
|
||||
file.WriteByte((byte)(byteRate >> 24));
|
||||
|
||||
// BlockAlign
|
||||
// NumChannels * BitsPerSample / 8
|
||||
int blockAlign = Channels * (BitsPerSample / 8);
|
||||
file.WriteByte((byte)(blockAlign >> 0));
|
||||
file.WriteByte((byte)(blockAlign >> 8));
|
||||
|
||||
// BitsPerSample
|
||||
file.WriteByte(16);
|
||||
file.WriteByte(0);
|
||||
|
||||
// Subchunk2ID "data"
|
||||
file.WriteByte(0x64);
|
||||
file.WriteByte(0x61);
|
||||
file.WriteByte(0x74);
|
||||
file.WriteByte(0x61);
|
||||
|
||||
// NumSamples * NumChannels * BitsPerSample/8
|
||||
int subchunk2Size = RecordBufferAt * 2 * (BitsPerSample / 8);
|
||||
file.WriteByte((byte)(subchunk2Size >> 0));
|
||||
file.WriteByte((byte)(subchunk2Size >> 8));
|
||||
file.WriteByte((byte)(subchunk2Size >> 16));
|
||||
file.WriteByte((byte)(subchunk2Size >> 24));
|
||||
|
||||
for (int i = 0; i < RecordBufferAt; i++) {
|
||||
file.WriteByte((byte)(RecordBuffer[i] >> 0));
|
||||
file.WriteByte((byte)(RecordBuffer[i] >> 8));
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user