Milestone 3 - Services API
This commit adds the Services API and implements some services. It also changes the name of the application to Skyline and replaces the icon.
48
.idea/codeStyles/Project.xml
generated
@ -8,9 +8,40 @@
|
||||
<option name="INDENT_PREPROCESSOR_DIRECTIVE" value="4" />
|
||||
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
|
||||
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||
<option name="FUNCTION_PARAMETERS_WRAP" value="0" />
|
||||
<option name="FUNCTION_CALL_ARGUMENTS_WRAP" value="0" />
|
||||
<option name="CLASS_CONSTRUCTOR_INIT_LIST_WRAP" value="0" />
|
||||
<option name="FUNCTION_NON_TOP_AFTER_RETURN_TYPE_WRAP" value="0" />
|
||||
<option name="FUNCTION_TOP_AFTER_RETURN_TYPE_WRAP" value="0" />
|
||||
<option name="FUNCTION_PARAMETERS_WRAP" value="5" />
|
||||
<option name="FUNCTION_CALL_ARGUMENTS_WRAP" value="5" />
|
||||
<option name="TEMPLATE_DECLARATION_STRUCT_WRAP" value="1" />
|
||||
<option name="TEMPLATE_DECLARATION_FUNCTION_WRAP" value="1" />
|
||||
<option name="TEMPLATE_CALL_ARGUMENTS_WRAP" value="5" />
|
||||
<option name="TEMPLATE_CALL_ARGUMENTS_ALIGN_MULTILINE" value="true" />
|
||||
<option name="ALIGN_INIT_LIST_IN_COLUMNS" value="false" />
|
||||
<option name="HEADER_GUARD_STYLE_PATTERN" value="${PROJECT_NAME}_${PROJECT_REL_PATH}_${FILE_NAME}_${EXT}" />
|
||||
<option name="MACROS_NAMING_CONVENTION">
|
||||
<value prefix="" style="PASCAL_CASE" suffix="" />
|
||||
</option>
|
||||
<option name="CLASSES_AND_STRUCTS_NAMING_CONVENTION">
|
||||
<value prefix="" style="PASCAL_CASE" suffix="" />
|
||||
</option>
|
||||
<option name="ENUMS_NAMING_CONVENTION">
|
||||
<value prefix="" style="PASCAL_CASE" suffix="" />
|
||||
</option>
|
||||
<option name="TYPEDEFS_NAMING_CONVENTION">
|
||||
<value prefix="" style="SNAKE_CASE" suffix="" />
|
||||
</option>
|
||||
<option name="UNIONS_NAMING_CONVENTION">
|
||||
<value prefix="" style="PASCAL_CASE" suffix="" />
|
||||
</option>
|
||||
<option name="METHODS_NAMING_CONVENTION">
|
||||
<value prefix="" style="PASCAL_CASE" suffix="" />
|
||||
</option>
|
||||
<option name="GLOBAL_FUNCTIONS_NAMING_CONVENTION">
|
||||
<value prefix="" style="PASCAL_CASE" suffix="" />
|
||||
</option>
|
||||
<option name="GLOBAL_VARIABLES_NAMING_CONVENTION">
|
||||
<value prefix="" style="PASCAL_CASE" suffix="" />
|
||||
</option>
|
||||
</Objective-C>
|
||||
<Objective-C-extensions>
|
||||
<extensions>
|
||||
@ -34,9 +65,18 @@
|
||||
<option name="WRAP_LONG_LINES" value="true" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ObjectiveC">
|
||||
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
|
||||
<option name="BLANK_LINES_AROUND_CLASS" value="0" />
|
||||
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="false" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="LABEL_INDENT_SIZE" value="-1" />
|
||||
<option name="LABEL_INDENT_SIZE" value="-2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
|
935
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,6 +1,941 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AbsoluteAlignmentInUserInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AbstractClassExtendsConcreteClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AbstractClassNeverImplemented" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AbstractClassWithOnlyOneDirectInheritor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AbstractClassWithoutAbstractMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AbstractMethodCallInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AbstractMethodOverridesAbstractMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AbstractMethodOverridesConcreteMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AbstractMethodWithMissingImplementations" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="nonThreadSafeClasses">
|
||||
<value />
|
||||
</option>
|
||||
<option name="nonThreadSafeTypes" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AmbiguousFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AmbiguousMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="Annotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnnotationClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnonymousClassComplexity" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="3" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AnonymousClassMethodCount" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="1" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AnonymousClassVariableHidesContainingMethodVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnonymousInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ArrayLengthInLoopCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertAsName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertEqualsMayBeAssertSame" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertMessageNotString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertsWithoutMessages" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertsWithoutMessagesTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentOrReturnOfFieldWithMutableType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToForLoopParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_checkForeachParameters" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTransformationOfOriginalParameter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AssignmentToNull" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToStaticFieldFromInstanceMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToSuperclassField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AutoBoxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAddedToCollection" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AutoUnboxing" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AwaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AwaitWithoutCorrespondingSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BadExceptionCaught" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="exceptionsString" value="" />
|
||||
<option name="exceptions">
|
||||
<value />
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BadExceptionDeclared" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="exceptionsString" value="" />
|
||||
<option name="exceptions">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreTestCases" value="false" />
|
||||
<option name="ignoreLibraryOverrides" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BadExceptionThrown" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="exceptionsString" value="" />
|
||||
<option name="exceptions">
|
||||
<value />
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BeforeClassOrAfterClassIsPublicStaticVoidNoArg" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BeforeOrAfterIsPublicVoidNoArg" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BigDecimalEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BigDecimalLegacyMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BlockMarkerComments" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BooleanMethodNameMustStartWithQuestion" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreBooleanMethods" value="false" />
|
||||
<option name="ignoreInAnnotationInterface" value="true" />
|
||||
<option name="onlyWarnOnBaseMethods" value="true" />
|
||||
<option name="questionString" value="add,are,can,check,contains,could,endsWith,equals,has,is,matches,must,put,remove,shall,should,startsWith,was,were,will,would" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BooleanParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BooleanVariableAlwaysNegated" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BoundedWildcard" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BreakStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BreakStatementWithLabel" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BusyWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallToSimpleGetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreGetterCallsOnOtherObjects" value="false" />
|
||||
<option name="onlyReportPrivateGetter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CallToSimpleSetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSetterCallsOnOtherObjects" value="false" />
|
||||
<option name="onlyReportPrivateSetter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CallToStringConcatCanBeReplacedByOperator" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallToSuspiciousStringMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CastThatLosesPrecision" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreIntegerCharCasts" value="false" />
|
||||
<option name="ignoreOverflowingByteCasts" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CastToConcreteClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ChainedMethodCall" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreFieldInitializations" value="true" />
|
||||
<option name="m_ignoreThisSuperCalls" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CharUsedInArithmeticContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CharacterComparison" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CheckForOutOfMemoryOnLargeArrayAllocation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CheckedExceptionClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassComplexity" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="80" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassCoupling" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_includeJavaClasses" value="false" />
|
||||
<option name="m_includeLibraryClasses" value="false" />
|
||||
<option name="m_limit" value="15" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassHasNoToStringMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="excludeClassNames" value="" />
|
||||
<option name="excludeException" value="true" />
|
||||
<option name="excludeDeprecated" value="true" />
|
||||
<option name="excludeEnum" value="false" />
|
||||
<option name="excludeAbstract" value="false" />
|
||||
<option name="excludeTestCode" value="false" />
|
||||
<option name="excludeInnerClasses" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassIndependentOfModule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassInheritanceDepth" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="2" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassInitializer" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassLoaderInstantiation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNamePrefixedWithPackageName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNameSameAsAncestorName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNestingDepth" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="1" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassNewInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassOnlyUsedInOneModule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassOnlyUsedInOnePackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassReferencesSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassUnconnectedToPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassWithMultipleLoggers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="loggerNamesString" value="java.util.logging.Logger,org.slf4j.Logger,org.apache.commons.logging.Log,org.apache.log4j.Logger,org.apache.logging.log4j.Logger" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassWithTooManyDependencies" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="10" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassWithTooManyDependents" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="10" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassWithTooManyTransitiveDependencies" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="35" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassWithTooManyTransitiveDependents" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="35" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassWithoutConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassWithoutLogger" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="loggerNamesString" value="java.util.logging.Logger,org.slf4j.Logger,org.apache.commons.logging.Log,org.apache.log4j.Logger,org.apache.logging.log4j.Logger" />
|
||||
<option name="ignoreSuperLoggers" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassWithoutNoArgConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreClassesWithNoConstructors" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CloneCallsConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneInNonCloneableClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneReturnsClassType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneableImplementsClone" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreCloneableDueToInheritance" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CollectionsFieldAccessReplaceableByMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CollectionsMustHaveInitialCapacity" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparatorNotSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparisonOfShortAndChar" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionalExpressionWithIdenticalBranches" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingFloatingPointLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConnectionResource" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantAssertCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantDeclaredInAbstractClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantDeclaredInInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantOnLHSOfComparison" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantOnRHSOfComparison" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantTestNGAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantValueVariableUse" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstructorCount" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreDeprecatedConstructors" value="false" />
|
||||
<option name="m_limit" value="5" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ContinueStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ContinueStatementWithLabel" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConvertJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConvertOldAnnotations" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CovariantEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CustomClassloader" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CustomSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CyclicPackageDependency" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CyclomaticComplexity" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="10" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DateToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DeclareCollectionAsInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreLocalVariables" value="false" />
|
||||
<option name="ignorePrivateMethodsAndFields" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DesignForExtension" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DisjointPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DollarSignInName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreOnVolatileVariables" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DoubleLiteralMayBeFloatLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DriverManagerGetConnection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateStringLiteralInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="MIN_STRING_LENGTH" value="5" />
|
||||
<option name="IGNORE_PROPERTY_KEYS" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DynamicRegexReplaceableByCompiledPattern" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptyClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreClassWithParameterization" value="false" />
|
||||
<option name="ignoreThrowables" value="true" />
|
||||
<option name="commentsAreContent" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EmptyDirectory" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptyInitializer" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EnumAsName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EnumClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EnumerationCanBeIteration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsAndHashcode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ErrorRethrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExceptionFromCatchWhichDoesntWrap" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreGetMessage" value="false" />
|
||||
<option name="ignoreCantWrap" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ExceptionNameDoesntEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExceptionPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExpectedExceptionNeverThrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExpectedExceptionNeverThrownTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsConcreteCollection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExternalizableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FeatureEnvy" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTestCases" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldAccessNotGuarded" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="countGettersAndSetters" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldCount" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_countConstantFields" value="false" />
|
||||
<option name="m_considerStaticFinalFieldsConstant" value="false" />
|
||||
<option name="myCountEnumConstants" value="false" />
|
||||
<option name="m_limit" value="10" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldHasSetterButNoGetter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldHidesSuperclassField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldMayBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldNotUsedInToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FinalMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FinalMethodInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTrivialFinalizers" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ForLoopWithMissingComponent" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreCollectionLoops" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ForeachStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HardcodedLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HibernateResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HtmlTagCanBeJavadocTag" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IfMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IfStatementWithIdenticalBranches" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IfStatementWithTooManyBranches" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="3" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IgnoredJUnitTest" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ImplicitCallToSuper" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreForObjectSubclasses" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ImplicitDefaultCharsetUsage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ImplicitNumericConversion" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreWideningConversions" value="false" />
|
||||
<option name="ignoreCharConversions" value="false" />
|
||||
<option name="ignoreConstantConversions" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InconsistentLanguageLevel" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IncrementDecrementUsedAsExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassOnInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInnerInterfaces" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InstanceGuardedByStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceVariableInitialization" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignorePrimitives" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InstanceVariableOfConcreteClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceVariableUninitializedUse" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignorePrimitives" value="false" />
|
||||
<option name="annotationNamesString" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofChain" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreInstanceofOnLibraryClasses" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofInterfaces" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstantiationOfUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IntLiteralMayBeLongLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IntegerMultiplicationImplicitCastToLong" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreNonOverflowingCompileTimeConstants" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InterfaceNeverImplemented" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreInterfacesThatOnlyDeclareConstants" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InterfaceWithOnlyOneDirectInheritor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IteratorHasNextCallsIteratorNext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCExecuteWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCPrepareStatementWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JNDIResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JUnit3StyleTestMethodInJUnit4Class" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnit5AssertionsConverter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnit5Converter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnitTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JavaLangImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="KeySetIterationMayUseEntrySet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LabeledStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LambdaParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LambdaParameterNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LambdaUnfriendlyMethodOverload" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LawOfDemeter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreLibraryCalls" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LimitedScopeInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ListIndexOfReplaceableByContains" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ListenerMayUseAdapter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="checkForEmptyMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LiteralAsArgToStringEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LoadLibraryWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_VARIABLES" value="true" />
|
||||
<option name="REPORT_PARAMETERS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LocalVariableHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
<option name="m_ignoreStaticMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LocalVariableNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreForLoopParameters" value="false" />
|
||||
<option name="m_ignoreCatchParameters" value="false" />
|
||||
<option name="m_regex" value="[a-z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="1" />
|
||||
<option name="m_maxLength" value="20" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LocalVariableOfConcreteClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LogStatementGuardedByLogCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LoggingConditionDisagreesWithLogStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LoopWithImplicitTerminationCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MagicCharacter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MagicNumber" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MalformedRegex" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MalformedSetUpTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MarkerInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodCallInLoopCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodCount" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="20" />
|
||||
<option name="ignoreGettersAndSetters" value="false" />
|
||||
<option name="ignoreOverridingMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodCoupling" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_includeJavaClasses" value="false" />
|
||||
<option name="m_includeLibraryClasses" value="false" />
|
||||
<option name="m_limit" value="10" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_onlyPrivateOrFinal" value="false" />
|
||||
<option name="m_ignoreEmptyMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodNameSameAsParentName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOnlyUsedFromInnerClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreMethodsAccessedFromAnonymousClass" value="false" />
|
||||
<option name="ignoreStaticMethodsFromNonStaticInnerClass" value="false" />
|
||||
<option name="onlyReportStaticMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodReturnAlwaysConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodReturnOfConcreteClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodWithMultipleLoops" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MigrateAssertToMatcherAssert" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedAssertEqualsArgumentsTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MissingDeprecatedAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MissingPackageInfo" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_requireAnnotationsFirst" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MisspelledEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MisspelledMethodName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ModuleWithTooFewClasses" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="10" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ModuleWithTooManyClasses" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="100" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MultipleDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreForLoopDeclarations" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MultipleExceptionsDeclaredOnTestMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MultipleReturnPointsPerMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreGuardClauses" value="false" />
|
||||
<option name="ignoreEqualsMethod" value="false" />
|
||||
<option name="m_limit" value="1" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MultipleTypedDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MultiplyOrDivideByPowerOfTwo" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="checkDivision" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NakedNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NativeMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreNegatedNullComparison" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NegatedConditionalExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NegatedEqualityExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NegatedIfElse" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreNegatedNullComparison" value="true" />
|
||||
<option name="m_ignoreNegatedZeroComparison" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NegativelyNamedBooleanVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedAssignment" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedConditionalExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedMethodCall" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreFieldInitializations" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NestedSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedTryStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestingDepth" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="5" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NewClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NewExceptionWithoutArguments" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NewMethodNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonBooleanMethodNameMayNotStartWithQuestion" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="questionString" value="add,are,can,check,contains,could,endsWith,equals,has,is,matches,must,put,remove,shall,should,startsWith,was,were,will,would" />
|
||||
<option name="ignoreBooleanMethods" value="false" />
|
||||
<option name="onlyWarnOnBaseMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonCommentSourceStatements" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="30" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldInImmutable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalGuard" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalStaticVariableUsedInClassInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonProtectedConstructorInAbstractClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreNonPublicClasses" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonPublicClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonReproducibleMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableFieldInSerializableClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonSerializableObjectBoundToHttpSession" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableObjectPassedToObjectStream" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonStaticFinalLogger" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="loggerClassName" value="java.util.logging.Logger,org.slf4j.Logger,org.apache.commons.logging.Log,org.apache.log4j.Logger,org.apache.logging.log4j.Logger" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonStaticInnerClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NoopMethodInAbstractClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NotifyCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NotifyWithoutCorrespondingWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NumericToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedClassInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedGlobalDeclarationInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedInstanceVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedMacroInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedMethodInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedPropertyInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedStructInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedTemplateParameterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectAllocationInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRequiredObsoleteCollectionTypes" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OptionalContainsCollection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OverloadedMethodsWithSameNumberOfParameters" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreInconvertibleTypes" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OverloadedVarargsMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OverlyComplexArithmeticExpression" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="6" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OverlyComplexBooleanExpression" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="3" />
|
||||
<option name="m_ignorePureConjunctionsDisjunctions" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OverlyLargePrimitiveArrayInitializer" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OverlyLongLambda" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OverlyStrongTypeCast" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreInMatchingInstanceof" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OverridableMethodCallDuringObjectConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OverriddenMethodCallDuringObjectConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageDotHtmlMayBePackageInfo" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageInMultipleModules" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageInfoWithoutPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z]*" />
|
||||
<option name="m_minLength" value="3" />
|
||||
<option name="m_maxLength" value="16" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PackageVisibleField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageVisibleInnerClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreEnums" value="false" />
|
||||
<option name="ignoreInterfaces" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PackageWithTooFewClasses" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="3" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PackageWithTooManyClasses" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="10" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
<option name="m_ignoreStaticMethodParametersHidingInstanceFields" value="true" />
|
||||
<option name="m_ignoreForConstructors" value="false" />
|
||||
<option name="m_ignoreForPropertySetters" value="false" />
|
||||
<option name="m_ignoreForAbstractMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ParameterNameDiffersFromOverriddenParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreSingleCharacterNames" value="false" />
|
||||
<option name="m_ignoreOverridesOfLibraryMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ParameterNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="1" />
|
||||
<option name="m_maxLength" value="20" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ParameterOfConcreteClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ParameterTypePreventsOverriding" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ParametersPerConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ParametersPerMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="5" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PointlessIndexOfComparison" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PrivateMemberAccessBetweenOuterAndInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PropertyValueSetToItself" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProtectedField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProtectedInnerClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreEnums" value="false" />
|
||||
<option name="ignoreInterfaces" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicConstructorInNonPublicClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicInnerClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreEnums" value="false" />
|
||||
<option name="ignoreInterfaces" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PublicMethodNotExposedInInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="onlyWarnIfContainingClassImplementsAnInterface" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PublicMethodWithoutLogging" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="loggerClassName" value="java.util.logging.Logger,org.slf4j.Logger,org.apache.commons.logging.Log,org.apache.log4j.Logger,org.apache.logging.log4j.Logger" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="QuestionableName" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="nameString" value="aa,abc,bad,bar,bar2,baz,baz1,baz2,baz3,bb,blah,bogus,bool,cc,dd,defau1t,dummy,dummy2,ee,fa1se,ff,foo,foo1,foo2,foo3,foobar,four,fred,fred1,fred2,gg,hh,hello,hello1,hello2,hello3,ii,nu11,one,silly,silly2,string,two,that,then,three,whi1e,var" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RandomDoubleForRandomInteger" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RawUseOfParameterizedType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadObjectAndWriteObjectPrivate" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadObjectInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadResolveAndWriteReplaceProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RecordStoreResource" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantFieldInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSerializable" value="false" />
|
||||
<option name="ignoreCloneable" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RedundantMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreLazyOperators" value="true" />
|
||||
<option name="ignoreObscureOperators" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ResultSetIndexZero" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReturnNull" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_reportObjectMethods" value="true" />
|
||||
<option name="m_reportArrayMethods" value="true" />
|
||||
<option name="m_reportCollectionMethods" value="true" />
|
||||
<option name="m_ignorePrivateMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReturnThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReuseOfLocalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RuntimeExec" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RuntimeExecWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SSBasedInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerialPersistentFieldsWithWrongSignature" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerialVersionUIDNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableDeserializableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableHasSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableInnerClassHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableInnerClassWithNonSerializableOuterClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableStoresNonSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableWithUnconstructableAncestor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SharedThreadLocalRandom" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SignalWithoutCorrespondingAwait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifiableEqualsExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SingleCharacterStartsWith" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SingleClassImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="Singleton" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StandardVariableNames" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticCallOnSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticCollection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreWeakCollections" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StaticFieldReferenceOnSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticGuardedByInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticInheritance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticMethodOnlyUsedInOneClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticNonFinalField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticSuite" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticVariableInitialization" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignorePrimitives" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StaticVariableOfConcreteClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticVariableUninitializedUse" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignorePrimitives" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StringBufferField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringBufferMustHaveInitialCapacity" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationArgumentToLogCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationInMessageFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyWarnOnLoop" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringTokenizer" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuperTearDownInFinally" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuppressionAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousGetterSetter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousLiteralUnderscore" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SwitchStatementDensity" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="20" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SwitchStatementWithTooManyBranches" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="10" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SynchronizationOnStaticField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_includeNativeMethods" value="true" />
|
||||
<option name="ignoreSynchronizedSuperMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemExit" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemOutErr" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemProperties" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemRunFinalizersOnExit" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemSetSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSupers" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestMethodIsPublicVoidNoArg" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestOnlyProblems" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TextLabelInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThisEscapedInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadDeathRethrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadDumpStack" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadLocalNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadRun" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadWithDefaultRunMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreeNegationsPerMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInEquals" value="true" />
|
||||
<option name="ignoreInAssert" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRethrownExceptions" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ThrowablePrintStackTrace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThrownExceptionsPerMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="3" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ThrowsRuntimeException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TimeToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TooBroadCatch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TooBroadThrows" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TransientFieldInNonSerializableClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TransientFieldNotInitialized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TypeMayBeWeakened" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="useRighthandTypeAsWeakestTypeInAssignments" value="true" />
|
||||
<option name="useParameterizedTypeForCollectionMethods" value="true" />
|
||||
<option name="doNotWeakenToJavaLangObject" value="true" />
|
||||
<option name="onlyWeakentoInterface" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TypeParameterExtendsFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UncheckedExceptionClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnconstructableTestCase" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UndeclaredTests" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnknownGuard" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreStaticFieldAccesses" value="false" />
|
||||
<option name="m_ignoreStaticMethodCalls" value="false" />
|
||||
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryExplicitNumericCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryFinalOnLocalVariableOrParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryInheritDoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryJavaDocLink" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreInlineLinkToSuper" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryQualifierForThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessarySuperConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessarySuperQualifier" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryUnicodeEscape" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnpredictableBigDecimalConstructorCall" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreReferences" value="true" />
|
||||
<option name="ignoreComplexLiterals" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnqualifiedInnerClassAccess" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreReferencesToLocalInnerClasses" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnqualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreStaticFieldAccesses" value="false" />
|
||||
<option name="m_ignoreStaticMethodCalls" value="false" />
|
||||
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnsecureRandomNumberGeneration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedExpressionResult" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedIncludeDirective" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedLibrary" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedLocalVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedLocalization" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedParameter" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedValue" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UpperCaseFieldNameNotConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfAWTPeerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfAnotherObjectsPrivateField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSameClass" value="false" />
|
||||
<option name="ignoreEquals" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UseOfClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfProcessBuilder" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfSunClasses" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UtilityClassCanBeEnum" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreClassesWithOnlyMain" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitNotifyNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitOrAwaitWithoutTimeout" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
10
.idea/misc.xml
generated
@ -9,7 +9,7 @@
|
||||
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="10">
|
||||
<list size="12">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
@ -20,12 +20,14 @@
|
||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||
<item index="10" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
||||
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="9">
|
||||
<list size="11">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
@ -35,11 +37,13 @@
|
||||
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||
<item index="9" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
||||
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
3
.idea/scopes/SkylineCPP.xml
generated
Normal file
@ -0,0 +1,3 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<scope name="SkylineCPP" pattern="file[app]:src/main/cpp//*&&!file[Lightswitch]:*&&!file[app]:*" />
|
||||
</component>
|
@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
project(Lightswitch VERSION 1 LANGUAGES CXX)
|
||||
project(Skyline VERSION 1 LANGUAGES CXX)
|
||||
|
||||
set(BUILD_TESTING OFF)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
@ -13,19 +13,24 @@ set(source_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)
|
||||
|
||||
include_directories(${source_DIR})
|
||||
|
||||
add_library(lightswitch SHARED
|
||||
${source_DIR}/lightswitch.cpp
|
||||
${source_DIR}/switch/common.cpp
|
||||
${source_DIR}/switch/nce.cpp
|
||||
${source_DIR}/switch/os.cpp
|
||||
${source_DIR}/switch/loader/nro.cpp
|
||||
${source_DIR}/switch/kernel/ipc.cpp
|
||||
${source_DIR}/switch/kernel/svc.cpp
|
||||
${source_DIR}/switch/kernel/service.cpp
|
||||
${source_DIR}/switch/kernel/types/KProcess.cpp
|
||||
${source_DIR}/switch/kernel/types/KThread.cpp
|
||||
${source_DIR}/switch/kernel/types/KSharedMemory.cpp
|
||||
${source_DIR}/switch/kernel/types/KPrivateMemory.cpp
|
||||
add_library(skyline SHARED
|
||||
${source_DIR}/main.cpp
|
||||
${source_DIR}/skyline/common.cpp
|
||||
${source_DIR}/skyline/nce.cpp
|
||||
${source_DIR}/skyline/os.cpp
|
||||
${source_DIR}/skyline/loader/nro.cpp
|
||||
${source_DIR}/skyline/kernel/ipc.cpp
|
||||
${source_DIR}/skyline/kernel/svc.cpp
|
||||
${source_DIR}/skyline/kernel/types/KSyncObject.cpp
|
||||
${source_DIR}/skyline/kernel/types/KProcess.cpp
|
||||
${source_DIR}/skyline/kernel/types/KThread.cpp
|
||||
${source_DIR}/skyline/kernel/types/KSharedMemory.cpp
|
||||
${source_DIR}/skyline/kernel/types/KPrivateMemory.cpp
|
||||
${source_DIR}/skyline/kernel/services/serviceman.cpp
|
||||
${source_DIR}/skyline/kernel/services/sm/sm.cpp
|
||||
${source_DIR}/skyline/kernel/services/set/sys.cpp
|
||||
${source_DIR}/skyline/kernel/services/apm/apm.cpp
|
||||
${source_DIR}/skyline/kernel/services/am/appletOE.cpp
|
||||
)
|
||||
target_link_libraries(lightswitch fmt tinyxml2 android)
|
||||
target_compile_options(lightswitch PRIVATE -Wno-c++17-extensions)
|
||||
target_link_libraries(skyline fmt tinyxml2)
|
||||
target_compile_options(skyline PRIVATE -Wno-c++17-extensions)
|
||||
|
@ -2,9 +2,9 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.0"
|
||||
buildToolsVersion '29.0.2'
|
||||
defaultConfig {
|
||||
applicationId "lightswitch.emu"
|
||||
applicationId "skyline.emu"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
@ -32,13 +32,17 @@ android {
|
||||
jni.srcDirs = ['src/main/cpp/unicorn/lib']
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.preference:preference:1.1.0-rc01'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.2.0'
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="emu.lightswitch">
|
||||
package="emu.skyline">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
|
||||
@ -9,29 +9,29 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:icon="@drawable/logo_skyline"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
android:fullBackupContent="@xml/backup_descriptor">
|
||||
<activity
|
||||
android:name=".LogActivity"
|
||||
android:name="emu.skyline.LogActivity"
|
||||
android:label="@string/log"
|
||||
android:parentActivityName=".MainActivity">
|
||||
android:parentActivityName="emu.skyline.MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="emu.lightswitch.MainActivity" />
|
||||
android:value="emu.skyline.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:name="emu.skyline.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:parentActivityName=".MainActivity">
|
||||
android:parentActivityName="emu.skyline.MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="emu.lightswitch.MainActivity" />
|
||||
android:value="emu.skyline.MainActivity" />
|
||||
</activity>
|
||||
<activity android:name=".MainActivity">
|
||||
<activity android:name="emu.skyline.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
@ -1,44 +0,0 @@
|
||||
#include <jni.h>
|
||||
#include <pthread.h>
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "switch/common.h"
|
||||
#include "switch/os.h"
|
||||
|
||||
std::thread *emu_thread;
|
||||
bool halt = false;
|
||||
|
||||
void thread_main(std::string rom_path, std::string pref_path, std::string log_path) {
|
||||
auto log = std::make_shared<lightSwitch::Logger>(log_path);
|
||||
auto settings = std::make_shared<lightSwitch::Settings>(pref_path);
|
||||
try {
|
||||
lightSwitch::kernel::OS os(log, settings);
|
||||
log->Write(lightSwitch::Logger::INFO, "Launching ROM {}", rom_path);
|
||||
os.Execute(rom_path);
|
||||
log->Write(lightSwitch::Logger::INFO, "Emulation has ended");
|
||||
} catch (std::exception &e) {
|
||||
log->Write(lightSwitch::Logger::ERROR, e.what());
|
||||
} catch (...) {
|
||||
log->Write(lightSwitch::Logger::ERROR, "An unknown exception has occurred");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_emu_lightswitch_MainActivity_loadFile(JNIEnv *env, jobject instance, jstring rom_path_, jstring pref_path_, jstring log_path_) {
|
||||
const char *rom_path = env->GetStringUTFChars(rom_path_, nullptr);
|
||||
const char *pref_path = env->GetStringUTFChars(pref_path_, nullptr);
|
||||
const char *log_path = env->GetStringUTFChars(log_path_, nullptr);
|
||||
|
||||
if (emu_thread) {
|
||||
halt = true; // This'll cause execution to stop after the next breakpoint
|
||||
emu_thread->join();
|
||||
halt = false; // Or the current instance will halt immediately
|
||||
}
|
||||
|
||||
// Running on UI thread is not a good idea as the UI will remain unresponsive
|
||||
emu_thread = new std::thread(thread_main, std::string(rom_path, strlen(rom_path)), std::string(pref_path, strlen(pref_path)), std::string(log_path, strlen(log_path)));
|
||||
|
||||
env->ReleaseStringUTFChars(rom_path_, rom_path);
|
||||
env->ReleaseStringUTFChars(pref_path_, pref_path);
|
||||
env->ReleaseStringUTFChars(log_path_, log_path);
|
||||
}
|
45
app/src/main/cpp/main.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include <jni.h>
|
||||
#include <pthread.h>
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "skyline/common.h"
|
||||
#include "skyline/os.h"
|
||||
|
||||
std::thread *EmuThread;
|
||||
bool Halt = false;
|
||||
|
||||
void ThreadMain(const std::string romPath, const std::string prefPath, const std::string logPath) {
|
||||
auto log = std::make_shared<skyline::Logger>(logPath);
|
||||
auto settings = std::make_shared<skyline::Settings>(prefPath);
|
||||
settings->List(log);
|
||||
try {
|
||||
skyline::kernel::OS os(log, settings);
|
||||
log->Write(skyline::Logger::Info, "Launching ROM {}", romPath);
|
||||
os.Execute(romPath);
|
||||
log->Write(skyline::Logger::Info, "Emulation has ended");
|
||||
} catch (std::exception &e) {
|
||||
log->Write(skyline::Logger::Error, e.what());
|
||||
} catch (...) {
|
||||
log->Write(skyline::Logger::Error, "An unknown exception has occurred");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_MainActivity_loadFile(JNIEnv *env, jobject instance, jstring romPathJni, jstring prefPathJni, jstring logPathJni) {
|
||||
const char *romPath = env->GetStringUTFChars(romPathJni, nullptr);
|
||||
const char *prefPath = env->GetStringUTFChars(prefPathJni, nullptr);
|
||||
const char *logPath = env->GetStringUTFChars(logPathJni, nullptr);
|
||||
|
||||
if (EmuThread) {
|
||||
Halt = true; // This'll cause execution to stop after the next breakpoint
|
||||
EmuThread->join();
|
||||
Halt = false; // Or the current instance will halt immediately
|
||||
}
|
||||
|
||||
// Running on UI thread is not a good idea as the UI will remain unresponsive
|
||||
EmuThread = new std::thread(ThreadMain, std::string(romPath, strlen(romPath)), std::string(prefPath, strlen(prefPath)), std::string(logPath, strlen(logPath)));
|
||||
|
||||
env->ReleaseStringUTFChars(romPathJni, romPath);
|
||||
env->ReleaseStringUTFChars(prefPathJni, prefPath);
|
||||
env->ReleaseStringUTFChars(logPathJni, logPath);
|
||||
}
|
67
app/src/main/cpp/skyline/common.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "common.h"
|
||||
#include <tinyxml2.h>
|
||||
#include <syslog.h>
|
||||
|
||||
namespace skyline {
|
||||
Settings::Settings(const std::string &prefXml) {
|
||||
tinyxml2::XMLDocument pref;
|
||||
if (pref.LoadFile(prefXml.c_str()))
|
||||
throw exception("TinyXML2 Error: " + std::string(pref.ErrorStr()));
|
||||
tinyxml2::XMLElement *elem = pref.LastChild()->FirstChild()->ToElement();
|
||||
while (elem) {
|
||||
switch (elem->Value()[0]) {
|
||||
case 's':
|
||||
stringMap.insert(
|
||||
std::pair<std::string, std::string>(elem->FindAttribute("name")->Value(), elem->GetText()));
|
||||
break;
|
||||
case 'b':
|
||||
boolMap.insert(std::pair<std::string, bool>(elem->FindAttribute("name")->Value(), elem->FindAttribute("value")->BoolValue()));
|
||||
default:
|
||||
break;
|
||||
};
|
||||
if (elem->NextSibling())
|
||||
elem = elem->NextSibling()->ToElement();
|
||||
else break;
|
||||
}
|
||||
pref.Clear();
|
||||
}
|
||||
|
||||
std::string Settings::GetString(const std::string& key) {
|
||||
return stringMap.at(key);
|
||||
}
|
||||
|
||||
bool Settings::GetBool(const std::string& key) {
|
||||
return boolMap.at(key);
|
||||
}
|
||||
|
||||
void Settings::List(std::shared_ptr<Logger>& logger) {
|
||||
for (auto& iter : stringMap)
|
||||
logger->Write(Logger::Info, "Key: {}, Value: {}, Type: String", iter.first, GetString(iter.first));
|
||||
for (auto& iter : boolMap)
|
||||
logger->Write(Logger::Info, "Key: {}, Value: {}, Type: Bool", iter.first, GetBool(iter.first));
|
||||
}
|
||||
|
||||
Logger::Logger(const std::string &logPath) {
|
||||
logFile.open(logPath, std::ios::app);
|
||||
WriteHeader("Logging started");
|
||||
}
|
||||
|
||||
Logger::~Logger() {
|
||||
WriteHeader("Logging ended");
|
||||
}
|
||||
|
||||
void Logger::WriteHeader(const std::string &str) {
|
||||
syslog(LOG_ALERT, "%s", str.c_str());
|
||||
logFile << "0|" << str << "\n";
|
||||
logFile.flush();
|
||||
}
|
||||
|
||||
void Logger::Write(const LogLevel level, const std::string &str) {
|
||||
#ifdef NDEBUG
|
||||
if (level == DEBUG) return;
|
||||
#endif
|
||||
syslog(levelSyslog[level], "%s", str.c_str());
|
||||
logFile << "1|" << levelStr[level] << "|" << str << "\n";
|
||||
logFile.flush();
|
||||
}
|
||||
}
|
260
app/src/main/cpp/skyline/common.h
Normal file
@ -0,0 +1,260 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <syslog.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <fmt/format.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace skyline {
|
||||
// Global typedefs
|
||||
typedef __uint128_t u128;
|
||||
typedef __uint64_t u64;
|
||||
typedef __uint32_t u32;
|
||||
typedef __uint16_t u16;
|
||||
typedef __uint8_t u8;
|
||||
typedef __int128_t i128;
|
||||
typedef __int64_t i64;
|
||||
typedef __int32_t i32;
|
||||
typedef __int16_t i16;
|
||||
typedef __int8_t i8;
|
||||
typedef std::runtime_error exception; //!< This is used as the default exception
|
||||
typedef u32 handle_t; //!< The type of an handle
|
||||
|
||||
namespace constant {
|
||||
// Memory
|
||||
constexpr u64 BaseAddr = 0x8000000; //!< The address space base
|
||||
constexpr u64 MapAddr = BaseAddr + 0x80000000; //!< The address of the map region
|
||||
constexpr u64 BaseSize = 0x7FF8000000; //!< The size of the address space
|
||||
constexpr u64 MapSize = 0x1000000000; //!< The size of the map region
|
||||
constexpr u64 TotalPhyMem = 0xF8000000; // ~4 GB of RAM
|
||||
constexpr size_t DefStackSize = 0x1E8480; //!< The default amount of stack: 2 MB
|
||||
constexpr size_t DefHeapSize = PAGE_SIZE; //!< The default amount of heap
|
||||
constexpr size_t TlsSlotSize = 0x200; //!< The size of a single TLS slot
|
||||
constexpr u8 TlsSlots = PAGE_SIZE / TlsSlotSize; //!< The amount of TLS slots in a single page
|
||||
// Loader
|
||||
constexpr u32 NroMagic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file
|
||||
// NCE
|
||||
constexpr u8 NumRegs = 31; //!< The amount of registers that ARMv8 has
|
||||
constexpr u16 SvcLast = 0x7F; //!< The index of the last SVC
|
||||
constexpr u16 BrkRdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
|
||||
constexpr u32 TpidrroEl0 = 0x5E83; //!< ID of TPIDRRO_EL0 in MRS
|
||||
// Kernel
|
||||
constexpr u64 MaxSyncHandles = 0x40; //!< The total amount of handles that can be passed to WaitSynchronization
|
||||
constexpr handle_t BaseHandleIndex = 0xD000; // The index of the base handle
|
||||
constexpr u8 DefaultPriority = 31; //!< The default priority of a process
|
||||
constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
|
||||
constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch
|
||||
// IPC
|
||||
constexpr size_t TlsIpcSize = 0x100; //!< The size of the IPC command buffer in a TLS slot
|
||||
constexpr u8 PortSize = 0x8; //!< The size of a port name string
|
||||
constexpr u32 SfcoMagic = 0x4F434653; //!< SFCO in reverse, written to IPC messages
|
||||
constexpr u32 SfciMagic = 0x49434653; //!< SFCI in reverse, present in received IPC messages
|
||||
constexpr u64 PaddingSum = 0x10; //!< The sum of the padding surrounding DataPayload
|
||||
constexpr handle_t BaseVirtualHandleIndex = 0x1; // The index of the base virtual handle
|
||||
// Status codes
|
||||
namespace status {
|
||||
constexpr u32 Success = 0x0; //!< "Success"
|
||||
constexpr u32 ServiceInvName = 0xC15; //!< "Invalid name"
|
||||
constexpr u32 ServiceNotReg = 0xE15; //!< "Service not registered"
|
||||
constexpr u32 InvAddress = 0xCC01; //!< "Invalid address"
|
||||
constexpr u32 InvHandle = 0xE401; //!< "Invalid handle"
|
||||
constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles"
|
||||
constexpr u32 Timeout = 0xEA01; //!< "Timeout while svcWaitSynchronization"
|
||||
constexpr u32 Unimpl = 0x177202; //!< "Unimplemented behaviour"
|
||||
}
|
||||
};
|
||||
|
||||
namespace instr {
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a BRK instruction. It can be used to generate as well as parse the instruction's opcode. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction.
|
||||
*/
|
||||
struct Brk {
|
||||
/**
|
||||
* Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
|
||||
* @param val The immediate value of the instruction
|
||||
*/
|
||||
Brk(u16 val) {
|
||||
start = 0x0; // First 5 bits of an BRK instruction are 0
|
||||
value = val;
|
||||
end = 0x6A1; // Last 11 bits of an BRK instruction stored as u16
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid BRK instruction
|
||||
*/
|
||||
bool Verify() {
|
||||
return (start == 0x0 && end == 0x6A1);
|
||||
}
|
||||
|
||||
u8 start : 5;
|
||||
u32 value : 16;
|
||||
u16 end : 11;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Brk) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call.
|
||||
*/
|
||||
struct Svc {
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid SVC instruction
|
||||
*/
|
||||
bool Verify() {
|
||||
return (start == 0x1 && end == 0x6A0);
|
||||
}
|
||||
|
||||
u8 start : 5;
|
||||
u32 value : 16;
|
||||
u16 end : 11;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Svc) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register.
|
||||
*/
|
||||
struct Mrs {
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid MRS instruction
|
||||
*/
|
||||
bool Verify() {
|
||||
return (end == 0xD53);
|
||||
}
|
||||
|
||||
u8 dstReg : 5;
|
||||
u32 srcReg : 15;
|
||||
u16 end : 12;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Mrs) == sizeof(u32));
|
||||
};
|
||||
|
||||
/**
|
||||
* Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers
|
||||
*/
|
||||
enum class Xreg { X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30 };
|
||||
enum class Wreg { W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, W16, W17, W18, W19, W20, W21, W22, W23, W24, W25, W26, W27, W28, W29, W30 };
|
||||
enum class Sreg { Sp, Pc, PState };
|
||||
|
||||
/**
|
||||
* @brief The Logger class is to generate a log of the program
|
||||
*/
|
||||
class Logger {
|
||||
private:
|
||||
std::ofstream logFile; //!< An output stream to the log file
|
||||
const char *levelStr[4] = {"0", "1", "2", "3"}; //!< This is used to denote the LogLevel when written out to a file
|
||||
static constexpr int levelSyslog[4] = {LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG}; //!< This corresponds to LogLevel and provides it's equivalent for syslog
|
||||
|
||||
public:
|
||||
enum LogLevel { Error, Warn, Info, Debug }; //!< The level of a particular log
|
||||
|
||||
/**
|
||||
* @param logPath The path to the log file
|
||||
*/
|
||||
Logger(const std::string &logPath);
|
||||
|
||||
/**
|
||||
* Writes "Logging ended" as a header
|
||||
*/
|
||||
~Logger();
|
||||
|
||||
/**
|
||||
* @brief Writes a header, should only be used for emulation starting and ending
|
||||
* @param str The value to be written
|
||||
*/
|
||||
void WriteHeader(const std::string &str);
|
||||
|
||||
/**
|
||||
* @brief Write a log to the log file
|
||||
* @param level The level of the log
|
||||
* @param str The value to be written
|
||||
*/
|
||||
void Write(const LogLevel level, const std::string &str);
|
||||
|
||||
/**
|
||||
* @brief Write a log to the log file with libfmt formatting
|
||||
* @param level The level of the log
|
||||
* @param formatStr The value to be written, with libfmt formatting
|
||||
* @param args The arguments based on format_str
|
||||
*/
|
||||
template<typename S, typename... Args>
|
||||
void Write(Logger::LogLevel level, const S &formatStr, Args &&... args) {
|
||||
#ifdef NDEBUG
|
||||
if (level == DEBUG) return;
|
||||
#endif
|
||||
Write(level, fmt::format(formatStr, args...));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The Settings class is used to access the parameters set in the Java component of the application
|
||||
*/
|
||||
class Settings {
|
||||
private:
|
||||
std::map<std::string, std::string> stringMap; //!< A mapping from all keys to their corresponding string value
|
||||
std::map<std::string, bool> boolMap; //!< A mapping from all keys to their corresponding boolean value
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param prefXml The path to the preference XML file
|
||||
*/
|
||||
Settings(const std::string &prefXml);
|
||||
|
||||
/**
|
||||
* @brief Retrieves a particular setting as a string
|
||||
* @param key The key of the setting
|
||||
* @return The string value of the setting
|
||||
*/
|
||||
std::string GetString(const std::string &key);
|
||||
|
||||
/**
|
||||
* @brief Retrieves a particular setting as a boolean
|
||||
* @param key The key of the setting
|
||||
* @return The boolean value of the setting
|
||||
*/
|
||||
bool GetBool(const std::string &key);
|
||||
|
||||
/**
|
||||
* @brief Writes all settings keys and values to syslog. This function is for development purposes.
|
||||
*/
|
||||
void List(std::shared_ptr<Logger> &logger);
|
||||
};
|
||||
|
||||
// Predeclare some classes here as we use them in DeviceState
|
||||
class NCE;
|
||||
namespace kernel {
|
||||
namespace type {
|
||||
class KProcess;
|
||||
class KThread;
|
||||
}
|
||||
class OS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This struct is used to hold the state of a device
|
||||
*/
|
||||
struct DeviceState {
|
||||
DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &thisProcess, std::shared_ptr<kernel::type::KThread> &thisThread, std::shared_ptr<NCE> nce, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger) : os(os), nce(nce), settings(settings), logger(logger), thisProcess(thisProcess), thisThread(thisThread) {}
|
||||
|
||||
kernel::OS *os; //!< This holds a reference to the OS class
|
||||
std::shared_ptr<kernel::type::KProcess> &thisProcess; //!< This holds a reference to the current process object
|
||||
std::shared_ptr<kernel::type::KThread> &thisThread; //!< This holds a reference to the current thread object
|
||||
std::shared_ptr<NCE> nce; //!< This holds a reference to the NCE class
|
||||
std::shared_ptr<Settings> settings; //!< This holds a reference to the Settings class
|
||||
std::shared_ptr<Logger> logger; //!< This holds a reference to the Logger class
|
||||
};
|
||||
}
|
148
app/src/main/cpp/skyline/kernel/ipc.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include <syslog.h>
|
||||
#include <cstdlib>
|
||||
#include "ipc.h"
|
||||
#include "types/KProcess.h"
|
||||
|
||||
namespace skyline::kernel::ipc {
|
||||
IpcRequest::IpcRequest(bool isDomain, const DeviceState &state) : isDomain(isDomain), state(state), tls() {
|
||||
u8 *currPtr = tls.data();
|
||||
state.thisProcess->ReadMemory(currPtr, state.thisThread->tls, constant::TlsIpcSize);
|
||||
|
||||
header = reinterpret_cast<CommandHeader *>(currPtr);
|
||||
currPtr += sizeof(CommandHeader);
|
||||
|
||||
if (header->handle_desc) {
|
||||
handleDesc = reinterpret_cast<HandleDescriptor *>(currPtr);
|
||||
currPtr += sizeof(HandleDescriptor) + (handleDesc->send_pid ? sizeof(u8) : 0);
|
||||
for (uint index = 0; handleDesc->copy_count > index; index++) {
|
||||
copyHandles.push_back(*reinterpret_cast<handle_t *>(currPtr));
|
||||
currPtr += sizeof(handle_t);
|
||||
}
|
||||
for (uint index = 0; handleDesc->move_count > index; index++) {
|
||||
moveHandles.push_back(*reinterpret_cast<handle_t *>(currPtr));
|
||||
currPtr += sizeof(handle_t);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint index = 0; header->x_no > index; index++) {
|
||||
vecBufX.push_back(reinterpret_cast<BufferDescriptorX *>(currPtr));
|
||||
currPtr += sizeof(BufferDescriptorX);
|
||||
}
|
||||
|
||||
for (uint index = 0; header->a_no > index; index++) {
|
||||
vecBufA.push_back(reinterpret_cast<BufferDescriptorABW *>(currPtr));
|
||||
currPtr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
|
||||
for (uint index = 0; header->b_no > index; index++) {
|
||||
vecBufB.push_back(reinterpret_cast<BufferDescriptorABW *>(currPtr));
|
||||
currPtr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
|
||||
for (uint index = 0; header->w_no > index; index++) {
|
||||
vecBufW.push_back(reinterpret_cast<BufferDescriptorABW *>(currPtr));
|
||||
currPtr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
|
||||
currPtr = reinterpret_cast<u8 *>((((reinterpret_cast<u64>(currPtr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::PaddingSum - 1U)) + constant::PaddingSum + reinterpret_cast<u64>(tls.data())); // Align to 16 bytes relative to start of TLS
|
||||
|
||||
if (isDomain) {
|
||||
domain = reinterpret_cast<DomainHeaderRequest *>(currPtr);
|
||||
currPtr += sizeof(DomainHeaderRequest);
|
||||
|
||||
payload = reinterpret_cast<PayloadHeader *>(currPtr);
|
||||
currPtr += sizeof(PayloadHeader);
|
||||
|
||||
cmdArg = currPtr;
|
||||
cmdArgSz = domain->payload_sz - sizeof(PayloadHeader);
|
||||
currPtr += domain->payload_sz;
|
||||
|
||||
for (uint index = 0; domain->input_count > index; index++) {
|
||||
domainObjects.push_back(*reinterpret_cast<handle_t *>(currPtr));
|
||||
currPtr += sizeof(handle_t);
|
||||
}
|
||||
} else {
|
||||
payload = reinterpret_cast<PayloadHeader *>(currPtr);
|
||||
currPtr += sizeof(PayloadHeader);
|
||||
|
||||
cmdArg = currPtr;
|
||||
cmdArgSz = (header->raw_sz * sizeof(u32)) - (constant::PaddingSum + sizeof(PayloadHeader));
|
||||
currPtr += cmdArgSz;
|
||||
}
|
||||
|
||||
if (payload->magic != constant::SfciMagic)
|
||||
state.logger->Write(Logger::Warn, "Unexpected Magic in PayloadHeader: 0x{:X}", u32(payload->magic));
|
||||
|
||||
if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) {
|
||||
vecBufC.push_back(reinterpret_cast<BufferDescriptorC *>(currPtr));
|
||||
} else if (header->c_flag > static_cast<u8>(BufferCFlag::SingleDescriptor)) {
|
||||
for (uint index = 0; (header->c_flag - 2) > index; index++) { // (c_flag - 2) C descriptors are present
|
||||
vecBufC.push_back(reinterpret_cast<BufferDescriptorC *>(currPtr));
|
||||
state.logger->Write(Logger::Debug, "Buf C #{} AD: 0x{:X} SZ: 0x{:X}", index, u64(vecBufC[index]->address), u16(vecBufC[index]->size));
|
||||
currPtr += sizeof(BufferDescriptorC);
|
||||
}
|
||||
}
|
||||
|
||||
state.logger->Write(Logger::Debug, "Header: X No: {}, A No: {}, B No: {}, W No: {}, C No: {}, Raw Size: {}", u8(header->x_no), u8(header->a_no), u8(header->b_no), u8(header->w_no), u8(vecBufC.size()), u64(cmdArgSz));
|
||||
if (header->handle_desc)
|
||||
state.logger->Write(Logger::Debug, "Handle Descriptor: Send PID: {}, Copy Count: {}, Move Count: {}", bool(handleDesc->send_pid), u32(handleDesc->copy_count), u32(handleDesc->move_count));
|
||||
if (isDomain)
|
||||
state.logger->Write(Logger::Debug, "Domain Header: Command: {}, Input Object Count: {}, Object ID: 0x{:X}", domain->command, domain->input_count, domain->object_id);
|
||||
state.logger->Write(Logger::Debug, "Data Payload: Command ID: 0x{:X}", u32(payload->value));
|
||||
}
|
||||
|
||||
IpcResponse::IpcResponse(bool isDomain, const DeviceState &state) : isDomain(isDomain), state(state) {}
|
||||
|
||||
void IpcResponse::WriteTls() {
|
||||
std::array<u8, constant::TlsIpcSize> tls{};
|
||||
u8 *currPtr = tls.data();
|
||||
auto header = reinterpret_cast<CommandHeader *>(currPtr);
|
||||
header->raw_sz = static_cast<u32>((sizeof(PayloadHeader) + argVec.size() + constant::PaddingSum + (isDomain ? sizeof(DomainHeaderRequest) : 0)) / sizeof(u32)); // Size is in 32-bit units because Nintendo
|
||||
header->handle_desc = (!copyHandles.empty() || !moveHandles.empty());
|
||||
currPtr += sizeof(CommandHeader);
|
||||
|
||||
if (header->handle_desc) {
|
||||
auto handleDesc = reinterpret_cast<HandleDescriptor *>(currPtr);
|
||||
handleDesc->copy_count = static_cast<u8>(copyHandles.size());
|
||||
handleDesc->move_count = static_cast<u8>(moveHandles.size());
|
||||
currPtr += sizeof(HandleDescriptor);
|
||||
|
||||
for (unsigned int copyHandle : copyHandles) {
|
||||
*reinterpret_cast<handle_t *>(currPtr) = copyHandle;
|
||||
currPtr += sizeof(handle_t);
|
||||
}
|
||||
|
||||
for (unsigned int moveHandle : moveHandles) {
|
||||
*reinterpret_cast<handle_t *>(currPtr) = moveHandle;
|
||||
currPtr += sizeof(handle_t);
|
||||
}
|
||||
}
|
||||
|
||||
u64 padding = ((((reinterpret_cast<u64>(currPtr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::PaddingSum - 1U)) + constant::PaddingSum + (reinterpret_cast<u64>(tls.data()) - reinterpret_cast<u64>(currPtr))); // Calculate the amount of padding at the front
|
||||
currPtr += padding;
|
||||
|
||||
if (isDomain) {
|
||||
auto domain = reinterpret_cast<DomainHeaderResponse *>(currPtr);
|
||||
domain->output_count = static_cast<u32>(domainObjects.size());
|
||||
currPtr += sizeof(DomainHeaderResponse);
|
||||
}
|
||||
|
||||
auto payload = reinterpret_cast<PayloadHeader *>(currPtr);
|
||||
payload->magic = constant::SfcoMagic;
|
||||
payload->version = 1;
|
||||
payload->value = errorCode;
|
||||
currPtr += sizeof(PayloadHeader);
|
||||
if (!argVec.empty())
|
||||
memcpy(currPtr, argVec.data(), argVec.size());
|
||||
currPtr += argVec.size();
|
||||
|
||||
if(isDomain) {
|
||||
for (auto& domainObject : domainObjects) {
|
||||
*reinterpret_cast<handle_t *>(currPtr) = domainObject;
|
||||
currPtr += sizeof(handle_t);
|
||||
}
|
||||
}
|
||||
|
||||
state.thisProcess->WriteMemory(tls.data(), state.thisThread->tls, constant::TlsIpcSize);
|
||||
}
|
||||
}
|
246
app/src/main/cpp/skyline/kernel/ipc.h
Normal file
@ -0,0 +1,246 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include "../common.h"
|
||||
|
||||
namespace skyline::kernel::ipc {
|
||||
/**
|
||||
* @brief This bit-field structure holds the header of an IPC command. (https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure)
|
||||
*/
|
||||
struct CommandHeader {
|
||||
u16 type : 16;
|
||||
u8 x_no : 4;
|
||||
u8 a_no : 4;
|
||||
u8 b_no : 4;
|
||||
u8 w_no : 4;
|
||||
u32 raw_sz : 10;
|
||||
u8 c_flag : 4;
|
||||
u32 : 17;
|
||||
bool handle_desc : 1;
|
||||
};
|
||||
static_assert(sizeof(CommandHeader) == 8);
|
||||
|
||||
/**
|
||||
* @brief This reflects the value in CommandStruct::type
|
||||
*/
|
||||
enum class CommandType : u16 {
|
||||
Invalid = 0, LegacyRequest = 1, Close = 2, LegacyControl = 3, Request = 4, Control = 5, RequestWithContext = 6, ControlWithContext = 7
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This reflects the value in CommandStruct::c_flags
|
||||
*/
|
||||
enum class BufferCFlag : u8 {
|
||||
None = 0, InlineDescriptor = 1, SingleDescriptor = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This bit-field structure holds the handle descriptor of a received IPC command. (https://switchbrew.org/wiki/IPC_Marshalling#Handle_descriptor)
|
||||
*/
|
||||
struct HandleDescriptor {
|
||||
bool send_pid : 1;
|
||||
u32 copy_count : 4;
|
||||
u32 move_count : 4;
|
||||
u32 : 23;
|
||||
};
|
||||
static_assert(sizeof(HandleDescriptor) == 4);
|
||||
|
||||
/**
|
||||
* @brief This bit-field structure holds the domain's header of an IPC request command. (https://switchbrew.org/wiki/IPC_Marshalling#Domains)
|
||||
*/
|
||||
struct DomainHeaderRequest {
|
||||
u8 command;
|
||||
u8 input_count;
|
||||
u16 payload_sz;
|
||||
u32 object_id;
|
||||
u32 : 32;
|
||||
u32 token;
|
||||
};
|
||||
static_assert(sizeof(DomainHeaderRequest) == 16);
|
||||
|
||||
/**
|
||||
* @brief This reflects the value of DomainHeaderRequest::command
|
||||
*/
|
||||
enum class DomainCommand : u8 {
|
||||
SendMessage = 1, CloseVHandle = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This bit-field structure holds the domain's header of an IPC response command. (https://switchbrew.org/wiki/IPC_Marshalling#Domains)
|
||||
*/
|
||||
struct DomainHeaderResponse {
|
||||
u32 output_count;
|
||||
u32 : 32;
|
||||
u64 : 64;
|
||||
};
|
||||
static_assert(sizeof(DomainHeaderResponse) == 16);
|
||||
|
||||
/**
|
||||
* @brief This bit-field structure holds the data payload of an IPC command. (https://switchbrew.org/wiki/IPC_Marshalling#Data_payload)
|
||||
*/
|
||||
struct PayloadHeader {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 value;
|
||||
u32 token;
|
||||
};
|
||||
static_assert(sizeof(PayloadHeader) == 16);
|
||||
|
||||
|
||||
/**
|
||||
* @brief This reflects which function PayloadHeader::value refers to when a control request is sent (https://switchbrew.org/wiki/IPC_Marshalling#Control)
|
||||
*/
|
||||
enum class ControlCommand : u32 {
|
||||
ConvertCurrentObjectToDomain = 0, CopyFromCurrentDomain = 1, CloneCurrentObject = 2, QueryPointerBufferSize = 3, CloneCurrentObjectEx = 4
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This is a buffer descriptor for X buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_X_.22Pointer.22
|
||||
*/
|
||||
struct BufferDescriptorX {
|
||||
u16 counter_0_5 : 6;
|
||||
u16 address_36_38 : 3;
|
||||
u16 counter_9_11 : 3;
|
||||
u16 address_32_35 : 4;
|
||||
u16 size : 16;
|
||||
u32 address_0_31 : 32;
|
||||
|
||||
BufferDescriptorX(u64 address, u16 counter, u16 size) : size(size) {
|
||||
// TODO: Test this, the AND mask might be the other way around
|
||||
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
|
||||
address_32_35 = static_cast<u16>(address & 0x78000000);
|
||||
address_36_38 = static_cast<u16>(address & 0x7000000);
|
||||
counter_0_5 = static_cast<u16>(address & 0x7E00);
|
||||
counter_9_11 = static_cast<u16>(address & 0x38);
|
||||
}
|
||||
|
||||
inline u64 Address() const {
|
||||
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_35) << 32 | static_cast<u64>(address_36_38) << 36;
|
||||
}
|
||||
|
||||
inline u16 Counter() const {
|
||||
return static_cast<u16>(counter_0_5) | static_cast<u16>(counter_9_11) << 9;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BufferDescriptorX) == 8);
|
||||
|
||||
/**
|
||||
* @brief This is a buffer descriptor for A/B/W buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_A.2FB.2FW_.22Send.22.2F.22Receive.22.2F.22Exchange.22
|
||||
*/
|
||||
struct BufferDescriptorABW {
|
||||
u32 size_0_31 : 32;
|
||||
u32 address_0_31 : 32;
|
||||
u8 flags : 2;
|
||||
u8 address_36_38 : 3;
|
||||
u32 : 19;
|
||||
u8 size_32_35 : 4;
|
||||
u8 address_32_35 : 4;
|
||||
|
||||
BufferDescriptorABW(u64 address, u64 size) {
|
||||
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
|
||||
address_32_35 = static_cast<u8>(address & 0x78000000);
|
||||
address_36_38 = static_cast<u8>(address & 0x7000000);
|
||||
size_0_31 = static_cast<u32>(size & 0x7FFFFFFF80000000);
|
||||
size_32_35 = static_cast<u8>(size & 0x78000000);
|
||||
}
|
||||
|
||||
inline u64 Address() const {
|
||||
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_35) << 32 | static_cast<u64>(address_36_38) << 36;
|
||||
}
|
||||
|
||||
inline u64 Size() const {
|
||||
return static_cast<u64>(size_0_31) | static_cast<u64>(size_32_35) << 32;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BufferDescriptorABW) == 12);
|
||||
|
||||
/**
|
||||
* @brief This is a buffer descriptor for C buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_C_.22ReceiveList.22
|
||||
*/
|
||||
struct BufferDescriptorC {
|
||||
u64 address : 48;
|
||||
u16 size : 16;
|
||||
|
||||
BufferDescriptorC(u64 address, u16 size) : address(address), size(size) {}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BufferDescriptorC) == 8);
|
||||
|
||||
/**
|
||||
* @brief This class encapsulates an IPC Request (https://switchbrew.org/wiki/IPC_Marshalling)
|
||||
*/
|
||||
class IpcRequest {
|
||||
private:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
|
||||
public:
|
||||
std::array<u8, constant::TlsIpcSize> tls; //!< A static-sized array where TLS data is actually copied to
|
||||
CommandHeader *header{}; //!< The header of the request
|
||||
HandleDescriptor *handleDesc{}; //!< The handle descriptor in case CommandHeader::handle_desc is true in the header
|
||||
bool isDomain{}; //!< If this is a domain request
|
||||
DomainHeaderRequest *domain{}; //!< In case this is a domain request, this holds data regarding it
|
||||
PayloadHeader *payload{}; //!< This is the header of the payload
|
||||
u8 *cmdArg{}; //!< This is a pointer to the data payload (End of PayloadHeader)
|
||||
u64 cmdArgSz{}; //!< This is the size of the data payload
|
||||
std::vector<handle_t> copyHandles; //!< A vector of handles that should be copied from the server to the client process (The difference is just to match application expectations, there is no real difference b/w copying and moving handles)
|
||||
std::vector<handle_t> moveHandles; //!< A vector of handles that should be moved from the server to the client process rather than copied
|
||||
std::vector<handle_t> domainObjects; //!< A vector of all input domain objects
|
||||
std::vector<BufferDescriptorX *> vecBufX; //!< This is a vector of pointers to X Buffer Descriptors
|
||||
std::vector<BufferDescriptorABW *> vecBufA; //!< This is a vector of pointers to A Buffer Descriptors
|
||||
std::vector<BufferDescriptorABW *> vecBufB; //!< This is a vector of pointers to B Buffer Descriptors
|
||||
std::vector<BufferDescriptorABW *> vecBufW; //!< This is a vector of pointers to W Buffer Descriptors
|
||||
std::vector<BufferDescriptorC *> vecBufC; //!< This is a vector of pointers to C Buffer Descriptors
|
||||
|
||||
/**
|
||||
* @param isDomain If the following request is a domain request
|
||||
* @param state The state of the device
|
||||
*/
|
||||
IpcRequest(bool isDomain, const DeviceState &state);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class encapsulates an IPC Response (https://switchbrew.org/wiki/IPC_Marshalling)
|
||||
*/
|
||||
class IpcResponse {
|
||||
private:
|
||||
std::vector<u8> argVec; //!< This holds all of the contents to be pushed to the payload
|
||||
const DeviceState &state; //!< The state of the device
|
||||
|
||||
public:
|
||||
bool isDomain{}; //!< If this is a domain request
|
||||
u32 errorCode{}; //!< The error code to respond with, it is 0 (Success) by default
|
||||
std::vector<handle_t> copyHandles; //!< A vector of handles to copy
|
||||
std::vector<handle_t> moveHandles; //!< A vector of handles to move
|
||||
std::vector<handle_t> domainObjects; //!< A vector of domain objects to write
|
||||
|
||||
/**
|
||||
* @param isDomain If the following request is a domain request
|
||||
* @param state The state of the device
|
||||
*/
|
||||
IpcResponse(bool isDomain, const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Writes an object to the payload
|
||||
* @tparam ValueType The type of the object to write
|
||||
* @param value The object to be written
|
||||
*/
|
||||
template<typename ValueType>
|
||||
void WriteValue(const ValueType &value) {
|
||||
argVec.reserve(argVec.size() + sizeof(ValueType));
|
||||
auto item = reinterpret_cast<const u8 *>(&value);
|
||||
for (uint index = 0; sizeof(ValueType) > index; index++) {
|
||||
argVec.push_back(*item);
|
||||
item++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes this IpcResponse object's contents into TLS
|
||||
*/
|
||||
void WriteTls();
|
||||
};
|
||||
}
|
19
app/src/main/cpp/skyline/kernel/services/am/appletOE.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "appletOE.h"
|
||||
|
||||
namespace skyline::kernel::service::am {
|
||||
appletOE::appletOE(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::apm, {
|
||||
{0x0, SFunc(appletOE::OpenApplicationProxy)}
|
||||
}) {}
|
||||
|
||||
void appletOE::OpenApplicationProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
manager.NewService(Service::am_IApplicationProxy, session, response);
|
||||
}
|
||||
|
||||
IApplicationProxy::IApplicationProxy(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::am_IApplicationProxy, {
|
||||
{0x0, SFunc(IApplicationProxy::GetCommonStateGetter)}
|
||||
}) {}
|
||||
|
||||
void IApplicationProxy::GetCommonStateGetter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
// TODO: This
|
||||
}
|
||||
}
|
32
app/src/main/cpp/skyline/kernel/services/am/appletOE.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "../base_service.h"
|
||||
#include "../serviceman.h"
|
||||
|
||||
namespace skyline::kernel::service::am {
|
||||
/**
|
||||
* @brief appletOE is used to open an application proxy (https://switchbrew.org/wiki/Applet_Manager_services#appletOE)
|
||||
*/
|
||||
class appletOE : public BaseService {
|
||||
public:
|
||||
appletOE(const DeviceState &state, ServiceManager& manager);
|
||||
|
||||
/**
|
||||
* @brief This returns IApplicationProxy (https://switchbrew.org/wiki/Applet_Manager_services#OpenApplicationProxy)
|
||||
*/
|
||||
void OpenApplicationProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IApplicationProxy returns handles to various services (https://switchbrew.org/wiki/Applet_Manager_services#IApplicationProxy)
|
||||
*/
|
||||
class IApplicationProxy : public BaseService {
|
||||
public:
|
||||
IApplicationProxy(const DeviceState &state, ServiceManager& manager);
|
||||
|
||||
/**
|
||||
* @brief This returns #ICommonStateGetter (https://switchbrew.org/wiki/Applet_Manager_services#ICommonStateGetter)
|
||||
*/
|
||||
void GetCommonStateGetter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
}
|
30
app/src/main/cpp/skyline/kernel/services/apm/apm.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "apm.h"
|
||||
|
||||
namespace skyline::kernel::service::apm {
|
||||
apm::apm(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::apm, {
|
||||
{0x0, SFunc(apm::OpenSession)}
|
||||
}) {}
|
||||
|
||||
void apm::OpenSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
manager.NewService(Service::apm_ISession, session, response);
|
||||
}
|
||||
|
||||
ISession::ISession(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::apm, {
|
||||
{0x0, SFunc(ISession::SetPerformanceConfiguration)},
|
||||
{0x1, SFunc(ISession::GetPerformanceConfiguration)}
|
||||
}) {}
|
||||
|
||||
void ISession::SetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
struct InputStruct {
|
||||
u32 mode;
|
||||
u32 config;
|
||||
} *performance = reinterpret_cast<InputStruct *>(request.cmdArg);
|
||||
performanceConfig[performance->mode] = performance->config;
|
||||
state.logger->Write(Logger::Info, "SetPerformanceConfiguration called with 0x{:X} ({})", performance->config, performance->mode ? "Docked" : "Handheld");
|
||||
}
|
||||
|
||||
void ISession::GetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
u32 performanceMode = *reinterpret_cast<u32 *>(request.cmdArg);
|
||||
response.WriteValue<u32>(performanceConfig[performanceMode]);
|
||||
}
|
||||
}
|
40
app/src/main/cpp/skyline/kernel/services/apm/apm.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "../base_service.h"
|
||||
#include "../serviceman.h"
|
||||
|
||||
namespace skyline::kernel::service::apm {
|
||||
/**
|
||||
* @brief apm is used to control performance modes of the device, this service however is mostly only used to open an ISession (https://switchbrew.org/wiki/PPC_services#apm)
|
||||
*/
|
||||
class apm : public BaseService {
|
||||
public:
|
||||
apm(const DeviceState &state, ServiceManager& manager);
|
||||
|
||||
/**
|
||||
* @brief This returns an handle to ISession
|
||||
*/
|
||||
void OpenSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief apm:ISession is a service opened when OpenSession is called by apm
|
||||
*/
|
||||
class ISession : public BaseService {
|
||||
private:
|
||||
u32 performanceConfig[2] = {0x00010000, 0x00020001}; //!< This holds the performance config for both handheld(0) and docked(1) mode
|
||||
|
||||
public:
|
||||
ISession(const DeviceState &state, ServiceManager& manager);
|
||||
|
||||
/**
|
||||
* @brief This sets performanceConfig to the given arguments, it doesn't affect anything else (https://switchbrew.org/wiki/PPC_services#SetPerformanceConfiguration)
|
||||
*/
|
||||
void SetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief This retrieves the particular performanceConfig for a mode and returns it to the client (https://switchbrew.org/wiki/PPC_services#SetPerformanceConfiguration)
|
||||
*/
|
||||
void GetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
}
|
70
app/src/main/cpp/skyline/kernel/services/base_service.h
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../common.h"
|
||||
#include "../ipc.h"
|
||||
#include <functional>
|
||||
|
||||
#define SFunc(function) std::bind(&function, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
class KSession;
|
||||
}
|
||||
namespace skyline::kernel::service {
|
||||
/**
|
||||
* @brief This contains an enum for every service that's present
|
||||
*/
|
||||
enum class Service {
|
||||
sm, set_sys, apm, apm_ISession, am_appletOE, am_IApplicationProxy
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A map from every service's name as a std::string to the corresponding serviceEnum
|
||||
*/
|
||||
const static std::unordered_map<std::string, Service> ServiceString = {
|
||||
{"sm:", Service::sm},
|
||||
{"set:sys", Service::set_sys},
|
||||
{"apm", Service::apm},
|
||||
{"appletOE", Service::am_appletOE},
|
||||
};
|
||||
|
||||
class ServiceManager;
|
||||
|
||||
/**
|
||||
* @brief The BaseService class is a class for all Services to inherit from
|
||||
*/
|
||||
class BaseService {
|
||||
protected:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
ServiceManager& manager; //!< A pointer to the service manager
|
||||
std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> vTable; //!< This holds the mapping from an object's CmdId to the actual function
|
||||
|
||||
public:
|
||||
Service serviceType; //!< Which service this is
|
||||
uint numSessions{}; //<! The amount of active sessions
|
||||
const bool hasLoop; //<! If the service has a loop or not
|
||||
|
||||
/**
|
||||
* @param state The state of the device
|
||||
* @param hasLoop If the service has a loop or not
|
||||
*/
|
||||
BaseService(const DeviceState &state, ServiceManager& manager, bool hasLoop, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable) : state(state), manager(manager), hasLoop(hasLoop), serviceType(serviceType), vTable(vTable) {}
|
||||
|
||||
/**
|
||||
* @brief This handles all IPC commands with type request to a service
|
||||
* @param request The corresponding IpcRequest object
|
||||
* @param response The corresponding IpcResponse object
|
||||
*/
|
||||
void HandleRequest(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
try {
|
||||
vTable.at(request.payload->value)(session, request, response);
|
||||
} catch (std::out_of_range&) {
|
||||
state.logger->Write(Logger::Warn, "Cannot find function in service with type {}: 0x{:X}", serviceType, u32(request.payload->value));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This is used by some services when they need to run some code at regular intervals
|
||||
*/
|
||||
virtual void Loop() {};
|
||||
};
|
||||
}
|
138
app/src/main/cpp/skyline/kernel/services/serviceman.cpp
Normal file
@ -0,0 +1,138 @@
|
||||
#include "serviceman.h"
|
||||
#include "../types/KProcess.h"
|
||||
#include "sm/sm.h"
|
||||
#include "set/sys.h"
|
||||
#include "apm/apm.h"
|
||||
#include "am/appletOE.h"
|
||||
|
||||
namespace skyline::kernel::service {
|
||||
ServiceManager::ServiceManager(const DeviceState &state) : state(state) {}
|
||||
|
||||
std::shared_ptr<BaseService> ServiceManager::GetService(const Service serviceType) {
|
||||
std::shared_ptr<BaseService> serviceObj;
|
||||
if (serviceMap.find(serviceType) == serviceMap.end()) {
|
||||
switch (serviceType) {
|
||||
case Service::sm:
|
||||
serviceMap[serviceType] = std::make_shared<sm::sm>(state, *this);
|
||||
break;
|
||||
case Service::set_sys:
|
||||
serviceMap[serviceType] = std::make_shared<set::sys>(state, *this);
|
||||
break;
|
||||
case Service::apm:
|
||||
serviceMap[serviceType] = std::make_shared<apm::apm>(state, *this);
|
||||
break;
|
||||
case Service::apm_ISession:
|
||||
serviceMap[serviceType] = std::make_shared<apm::ISession>(state, *this);
|
||||
break;
|
||||
case Service::am_appletOE:
|
||||
serviceMap[serviceType] = std::make_shared<am::appletOE>(state, *this);
|
||||
break;
|
||||
case Service::am_IApplicationProxy:
|
||||
serviceMap[serviceType] = std::make_shared<am::IApplicationProxy>(state, *this);
|
||||
break;
|
||||
}
|
||||
serviceObj = serviceMap[serviceType];
|
||||
} else
|
||||
serviceObj = serviceMap.at(serviceType);
|
||||
serviceObj->numSessions++;
|
||||
return serviceObj;
|
||||
}
|
||||
|
||||
handle_t ServiceManager::NewSession(const Service serviceType) {
|
||||
return state.thisProcess->NewHandle<type::KSession>(GetService(serviceType), serviceType)->handle;
|
||||
}
|
||||
|
||||
void ServiceManager::NewService(const Service serviceType, type::KSession &session, ipc::IpcResponse &response) {
|
||||
if (response.isDomain) {
|
||||
session.domainTable[++session.handleIndex] = GetService(serviceType);
|
||||
response.domainObjects.push_back(session.handleIndex);
|
||||
} else
|
||||
response.moveHandles.push_back(state.thisProcess->NewHandle<type::KSession>(GetService(serviceType), serviceType)->handle);
|
||||
}
|
||||
|
||||
void ServiceManager::CloseSession(const handle_t handle) {
|
||||
auto object = state.thisProcess->GetHandle<type::KSession>(handle);
|
||||
if (object->serviceStatus == type::KSession::ServiceStatus::Open) {
|
||||
if (object->isDomain) {
|
||||
for (const auto &service : object->domainTable)
|
||||
if (!(service.second->numSessions--))
|
||||
serviceMap.erase(service.second->serviceType);
|
||||
} else if (!(serviceMap.at(object->serviceType)->numSessions--))
|
||||
serviceMap.erase(object->serviceType);
|
||||
object->serviceStatus = type::KSession::ServiceStatus::Closed;
|
||||
}
|
||||
};
|
||||
|
||||
void ServiceManager::Loop() {
|
||||
for (auto&[index, service] : serviceMap)
|
||||
if (service->hasLoop)
|
||||
service->Loop();
|
||||
}
|
||||
|
||||
void ServiceManager::SyncRequestHandler(const handle_t handle) {
|
||||
auto session = state.thisProcess->GetHandle<type::KSession>(handle);
|
||||
state.logger->Write(Logger::Debug, "----Start----");
|
||||
state.logger->Write(Logger::Debug, "Handle is 0x{:X}", handle);
|
||||
|
||||
if (session->serviceStatus == type::KSession::ServiceStatus::Open) {
|
||||
ipc::IpcRequest request(session->isDomain, state);
|
||||
ipc::IpcResponse response(session->isDomain, state);
|
||||
|
||||
switch (static_cast<ipc::CommandType>(request.header->type)) {
|
||||
case ipc::CommandType::Request:
|
||||
case ipc::CommandType::RequestWithContext:
|
||||
if (session->isDomain) {
|
||||
try {
|
||||
auto service = session->domainTable.at(request.domain->object_id);
|
||||
switch (static_cast<ipc::DomainCommand>(request.domain->command)) {
|
||||
case ipc::DomainCommand::SendMessage:
|
||||
service->HandleRequest(*session, request, response);
|
||||
break;
|
||||
case ipc::DomainCommand::CloseVHandle:
|
||||
if (!(service->numSessions--))
|
||||
serviceMap.erase(service->serviceType);
|
||||
session->domainTable.erase(request.domain->object_id);
|
||||
break;
|
||||
}
|
||||
} catch (std::out_of_range&) {
|
||||
throw exception("Invalid object ID was used with domain request");
|
||||
}
|
||||
} else
|
||||
session->serviceObject->HandleRequest(*session, request, response);
|
||||
response.WriteTls();
|
||||
break;
|
||||
|
||||
case ipc::CommandType::Control:
|
||||
case ipc::CommandType::ControlWithContext:
|
||||
state.logger->Write(Logger::Debug, "Control IPC Message: {}", request.payload->value);
|
||||
switch (static_cast<ipc::ControlCommand>(request.payload->value)) {
|
||||
case ipc::ControlCommand::ConvertCurrentObjectToDomain:
|
||||
response.WriteValue(session->ConvertDomain());
|
||||
break;
|
||||
case ipc::ControlCommand::CloneCurrentObject:
|
||||
case ipc::ControlCommand::CloneCurrentObjectEx:
|
||||
CloneSession(*session, request, response);
|
||||
break;
|
||||
default:
|
||||
throw exception(fmt::format("Unknown Control Command: {}", request.payload->value));
|
||||
}
|
||||
response.WriteTls();
|
||||
break;
|
||||
|
||||
case ipc::CommandType::Close:
|
||||
state.logger->Write(Logger::Debug, "Closing Session");
|
||||
CloseSession(handle);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw exception(fmt::format("Unimplemented IPC message type: {}", u16(request.header->type)));
|
||||
}
|
||||
} else
|
||||
state.logger->Write(Logger::Warn, "svcSendSyncRequest called on closed handle: 0x{:X}", handle);
|
||||
state.logger->Write(Logger::Debug, "====End====");
|
||||
}
|
||||
|
||||
void ServiceManager::CloneSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
NewService(session.serviceType, session, response);
|
||||
}
|
||||
}
|
62
app/src/main/cpp/skyline/kernel/services/serviceman.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../nce.h"
|
||||
#include "base_service.h"
|
||||
#include "../types/KSession.h"
|
||||
|
||||
namespace skyline::kernel::service {
|
||||
/**
|
||||
* @brief The ServiceManager class manages passing IPC requests to the right Service and running event loops of Services
|
||||
*/
|
||||
class ServiceManager {
|
||||
private:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
std::unordered_map<Service, std::shared_ptr<BaseService>> serviceMap; //!< A map from it's type to a BaseService object
|
||||
|
||||
std::shared_ptr<BaseService> GetService(const Service serviceType);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param state The state of the device
|
||||
*/
|
||||
ServiceManager(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Creates a new service and returns it's handle
|
||||
* @param serviceType The type of the service
|
||||
* @return Handle to KService object of the service
|
||||
*/
|
||||
handle_t NewSession(const Service serviceType);
|
||||
|
||||
/**
|
||||
* @brief Creates a new service and writes it's handle or virtual handle (If it's a domain request) to IpcResponse
|
||||
* @param serviceType The type of the service
|
||||
* @param session The session object of the command
|
||||
* @param response The response object to write the handle or virtual handle to
|
||||
*/
|
||||
void NewService(const Service serviceType, type::KSession &session, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Closes an existing session to a service
|
||||
* @param service The handle of the KService object
|
||||
*/
|
||||
void CloseSession(const handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief This is a function where the Services get to run their core event loops
|
||||
*/
|
||||
void Loop();
|
||||
|
||||
/**
|
||||
* @brief Handles a Synchronous IPC Request
|
||||
* @param handle The handle of the object
|
||||
*/
|
||||
void SyncRequestHandler(const handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Duplicates a session
|
||||
* @param handle The handle of the object
|
||||
*/
|
||||
void CloneSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
}
|
11
app/src/main/cpp/skyline/kernel/services/set/sys.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "sys.h"
|
||||
#include "../../types/KProcess.h"
|
||||
|
||||
namespace skyline::kernel::service::set {
|
||||
sys::sys(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::set_sys, {{0x3, SFunc(sys::GetFirmwareVersion)}}) {}
|
||||
|
||||
void sys::GetFirmwareVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
static SysVerTitle title{.minor=9, .major=0, .micro=0, .rev_major=4, .platform="NX", .ver_hash="4de65c071fd0869695b7629f75eb97b2551dbf2f", .disp_ver="9.0.0", .disp_title="NintendoSDK Firmware for NX 9.0.0-4.0"};
|
||||
state.thisProcess->WriteMemory(title, request.vecBufC[0]->address);
|
||||
}
|
||||
}
|
38
app/src/main/cpp/skyline/kernel/services/set/sys.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "../base_service.h"
|
||||
#include "../serviceman.h"
|
||||
|
||||
namespace skyline::kernel::service::set {
|
||||
/**
|
||||
* @brief set:sys or System Settings service provides access to system settings
|
||||
*/
|
||||
class sys : public BaseService {
|
||||
private:
|
||||
/**
|
||||
* @brief Encapsulates the system version, this is sent to the application in GetFirmwareVersion (https://switchbrew.org/wiki/System_Version_Title)
|
||||
*/
|
||||
struct SysVerTitle {
|
||||
u8 major;
|
||||
u8 minor;
|
||||
u8 micro;
|
||||
u8 : 8;
|
||||
u8 rev_major;
|
||||
u8 rev_minor;
|
||||
u16 : 16;
|
||||
u8 platform[0x20];
|
||||
u8 ver_hash[0x40];
|
||||
u8 disp_ver[0x18];
|
||||
u8 disp_title[0x80];
|
||||
};
|
||||
static_assert(sizeof(SysVerTitle) == 0x100);
|
||||
|
||||
public:
|
||||
sys(const DeviceState &state, ServiceManager& manager);
|
||||
|
||||
/**
|
||||
* @brief Writes the Firmware version to a 0xA buffer (https://switchbrew.org/wiki/Settings_services#GetFirmwareVersion)
|
||||
*/
|
||||
void GetFirmwareVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
}
|
25
app/src/main/cpp/skyline/kernel/services/sm/sm.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include "sm.h"
|
||||
|
||||
namespace skyline::kernel::service::sm {
|
||||
sm::sm(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::sm, {
|
||||
{0x0, SFunc(sm::Initialize)},
|
||||
{0x1, SFunc(sm::GetService)}
|
||||
}) {}
|
||||
|
||||
void sm::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {}
|
||||
|
||||
void sm::GetService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
std::string serviceName(reinterpret_cast<char *>(request.cmdArg));
|
||||
if (serviceName.empty())
|
||||
response.errorCode = constant::status::ServiceInvName;
|
||||
else {
|
||||
try {
|
||||
manager.NewService(ServiceString.at(serviceName), session, response);
|
||||
state.logger->Write(Logger::Debug, "Service has been registered: \"{}\"", serviceName);
|
||||
} catch (std::out_of_range &) {
|
||||
response.errorCode = constant::status::ServiceNotReg;
|
||||
state.logger->Write(Logger::Error, "Service has not been implemented: \"{}\"", serviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
app/src/main/cpp/skyline/kernel/services/sm/sm.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../base_service.h"
|
||||
#include "../serviceman.h"
|
||||
|
||||
namespace skyline::kernel::service::sm {
|
||||
/**
|
||||
* @brief sm: or Service Manager is responsible for providing handles to services (https://switchbrew.org/wiki/Services_API)
|
||||
*/
|
||||
class sm : public BaseService {
|
||||
public:
|
||||
sm(const DeviceState &state, ServiceManager& manager);
|
||||
|
||||
/**
|
||||
* @brief This initializes the sm: service. It doesn't actually do anything. (https://switchbrew.org/wiki/Services_API#Initialize)
|
||||
*/
|
||||
void Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief This returns a handle to a service with it's name passed in as an argument (https://switchbrew.org/wiki/Services_API#GetService)
|
||||
*/
|
||||
void GetService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
}
|
184
app/src/main/cpp/skyline/kernel/svc.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <syslog.h>
|
||||
#include <utility>
|
||||
#include "svc.h"
|
||||
#include "../os.h"
|
||||
|
||||
namespace skyline::kernel::svc {
|
||||
void SetHeapSize(DeviceState &state) {
|
||||
auto heap = state.thisProcess->MapPrivateRegion(0, state.nce->GetRegister(Wreg::W1), {true, true, false}, memory::Type::Heap, memory::Region::Heap);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.nce->SetRegister(Xreg::X1, heap->address);
|
||||
state.logger->Write(Logger::Debug, "Heap size was set to 0x{:X}", state.nce->GetRegister(Wreg::W1));
|
||||
}
|
||||
|
||||
void QueryMemory(DeviceState &state) {
|
||||
memory::MemoryInfo memInf;
|
||||
bool found{};
|
||||
u64 addr = state.nce->GetRegister(Xreg::X2);
|
||||
for(auto& [region, sharedMem] : state.nce->memoryRegionMap) {
|
||||
if (addr == sharedMem->address) {
|
||||
memInf = sharedMem->GetInfo(state.thisProcess->mainThread);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
if (state.thisProcess->memoryMap.count(addr))
|
||||
memInf = state.thisProcess->memoryMap.at(addr)->GetInfo();
|
||||
else {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
return;
|
||||
}
|
||||
}
|
||||
state.thisProcess->WriteMemory<memory::MemoryInfo>(memInf, state.nce->GetRegister(Xreg::X0));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
}
|
||||
|
||||
void CreateThread(DeviceState &state) {
|
||||
// TODO: Check if the values supplied by the process are actually valid & Support Core Mask potentially ?
|
||||
auto thread = state.thisProcess->CreateThread(state.nce->GetRegister(Xreg::X1), state.nce->GetRegister(Xreg::X2), state.nce->GetRegister(Xreg::X3), static_cast<u8>(state.nce->GetRegister(Wreg::W4)));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.nce->SetRegister(Wreg::W1, thread->handle);
|
||||
}
|
||||
|
||||
void StartThread(DeviceState &state) {
|
||||
auto &object = state.thisProcess->handleTable.at(static_cast<const unsigned int &>(state.nce->GetRegister(Wreg::W0)));
|
||||
if (object->handleType == type::KType::KThread)
|
||||
std::static_pointer_cast<type::KThread>(object)->Start();
|
||||
else
|
||||
throw exception("StartThread was called on a non-KThread object");
|
||||
}
|
||||
|
||||
void ExitThread(DeviceState &state) {
|
||||
state.os->KillThread(state.thisThread->pid);
|
||||
}
|
||||
|
||||
void GetThreadPriority(DeviceState &state) {
|
||||
state.nce->SetRegister(Wreg::W1, state.thisProcess->GetHandle<type::KThread>(static_cast<handle_t>(state.nce->GetRegister(Wreg::W0)))->priority);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
}
|
||||
|
||||
void SetThreadPriority(DeviceState &state) {
|
||||
state.thisProcess->GetHandle<type::KThread>(static_cast<handle_t>(state.nce->GetRegister(Wreg::W0)))->Start();
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
}
|
||||
|
||||
void CloseHandle(DeviceState &state) {
|
||||
auto handle = static_cast<handle_t>(state.nce->GetRegister(Wreg::W0));
|
||||
state.logger->Write(Logger::Debug, "Closing handle: 0x{:X}", handle);
|
||||
auto &object = state.thisProcess->handleTable.at(handle);
|
||||
switch (object->handleType) {
|
||||
case (type::KType::KThread):
|
||||
state.os->KillThread(std::static_pointer_cast<type::KThread>(object)->pid);
|
||||
break;
|
||||
case (type::KType::KProcess):
|
||||
state.os->KillThread(std::static_pointer_cast<type::KProcess>(object)->mainThread);
|
||||
break;
|
||||
default:
|
||||
state.thisProcess->handleTable.erase(handle);
|
||||
}
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
}
|
||||
|
||||
void WaitSynchronization(DeviceState &state) {
|
||||
state.thisThread->timeoutEnd = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() + state.nce->GetRegister(Xreg::X3);
|
||||
auto numHandles = state.nce->GetRegister(Wreg::W2);
|
||||
if (numHandles > constant::MaxSyncHandles) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::MaxHandles);
|
||||
return;
|
||||
}
|
||||
state.thisThread->waitHandles.reserve(numHandles);
|
||||
state.thisProcess->ReadMemory(state.thisThread->waitHandles.data(), state.nce->GetRegister(Xreg::X1), numHandles * sizeof(handle_t));
|
||||
state.thisThread->status = type::KThread::ThreadStatus::Waiting;
|
||||
}
|
||||
|
||||
void ConnectToNamedPort(DeviceState &state) {
|
||||
char port[constant::PortSize + 1]{0}; // +1 so string will always be null terminated
|
||||
state.os->thisProcess->ReadMemory(port, state.nce->GetRegister(Xreg::X1), constant::PortSize);
|
||||
if (std::strcmp(port, "sm:") == 0)
|
||||
state.nce->SetRegister(Wreg::W1, state.os->serviceManager.NewSession(service::Service::sm));
|
||||
else
|
||||
throw exception(fmt::format("svcConnectToNamedPort tried connecting to invalid port: \"{}\"", port));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
}
|
||||
|
||||
void SendSyncRequest(DeviceState &state) {
|
||||
state.os->serviceManager.SyncRequestHandler(static_cast<handle_t>(state.nce->GetRegister(Xreg::X0)));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
}
|
||||
|
||||
void OutputDebugString(DeviceState &state) {
|
||||
std::string debug(state.nce->GetRegister(Xreg::X1), '\0');
|
||||
state.os->thisProcess->ReadMemory((void *) debug.data(), state.nce->GetRegister(Xreg::X0), state.nce->GetRegister(Xreg::X1));
|
||||
state.logger->Write(Logger::Info, "svcOutputDebugString: {}", debug.c_str());
|
||||
state.nce->SetRegister(Wreg::W0, 0);
|
||||
}
|
||||
|
||||
void GetInfo(DeviceState &state) {
|
||||
state.logger->Write(Logger::Debug, "svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(Wreg::W1), state.nce->GetRegister(Xreg::X3));
|
||||
switch (state.nce->GetRegister(Wreg::W1)) {
|
||||
case constant::infoState::AllowedCpuIdBitmask:
|
||||
case constant::infoState::AllowedThreadPriorityMask:
|
||||
case constant::infoState::IsCurrentProcessBeingDebugged:
|
||||
case constant::infoState::TitleId:
|
||||
case constant::infoState::PrivilegedProcessId:
|
||||
state.nce->SetRegister(Xreg::X1, 0);
|
||||
break;
|
||||
case constant::infoState::AliasRegionBaseAddr:
|
||||
state.nce->SetRegister(Xreg::X1, constant::MapAddr);
|
||||
break;
|
||||
case constant::infoState::AliasRegionSize:
|
||||
state.nce->SetRegister(Xreg::X1, constant::MapSize);
|
||||
break;
|
||||
case constant::infoState::HeapRegionBaseAddr:
|
||||
state.nce->SetRegister(Xreg::X1, state.os->thisProcess->memoryRegionMap.at(memory::Region::Heap)->address);
|
||||
break;
|
||||
case constant::infoState::HeapRegionSize:
|
||||
state.nce->SetRegister(Xreg::X1, state.os->thisProcess->memoryRegionMap.at(memory::Region::Heap)->size);
|
||||
break;
|
||||
case constant::infoState::TotalMemoryAvailable:
|
||||
state.nce->SetRegister(Xreg::X1, constant::TotalPhyMem);
|
||||
break;
|
||||
case constant::infoState::TotalMemoryUsage:
|
||||
state.nce->SetRegister(Xreg::X1, state.os->thisProcess->memoryRegionMap.at(memory::Region::Heap)->address + state.thisProcess->mainThreadStackSz + state.nce->GetSharedSize());
|
||||
break;
|
||||
case constant::infoState::AddressSpaceBaseAddr:
|
||||
state.nce->SetRegister(Xreg::X1, constant::BaseAddr);
|
||||
break;
|
||||
case constant::infoState::AddressSpaceSize:
|
||||
state.nce->SetRegister(Xreg::X1, constant::BaseSize);
|
||||
break;
|
||||
case constant::infoState::StackRegionBaseAddr:
|
||||
state.nce->SetRegister(Xreg::X1, state.thisThread->stackTop);
|
||||
break;
|
||||
case constant::infoState::StackRegionSize:
|
||||
state.nce->SetRegister(Xreg::X1, state.thisProcess->mainThreadStackSz);
|
||||
break;
|
||||
case constant::infoState::PersonalMmHeapSize:
|
||||
state.nce->SetRegister(Xreg::X1, constant::TotalPhyMem);
|
||||
break;
|
||||
case constant::infoState::PersonalMmHeapUsage:
|
||||
state.nce->SetRegister(Xreg::X1, state.os->thisProcess->memoryRegionMap.at(memory::Region::Heap)->address + state.thisProcess->mainThreadStackSz);
|
||||
break;
|
||||
case constant::infoState::TotalMemoryAvailableWithoutMmHeap:
|
||||
state.nce->SetRegister(Xreg::X1, constant::TotalPhyMem); // TODO: NPDM specifies SystemResourceSize, subtract that from this
|
||||
break;
|
||||
case constant::infoState::TotalMemoryUsedWithoutMmHeap:
|
||||
state.nce->SetRegister(Xreg::X1, state.os->thisProcess->memoryRegionMap.at(memory::Region::Heap)->address + state.thisProcess->mainThreadStackSz); // TODO: Same as above
|
||||
break;
|
||||
case constant::infoState::UserExceptionContextAddr:
|
||||
state.nce->SetRegister(Xreg::X1, state.thisProcess->tlsPages[0]->Get(0));
|
||||
break;
|
||||
default:
|
||||
state.logger->Write(Logger::Warn, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(Wreg::W1), state.nce->GetRegister(Xreg::X3));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Unimpl);
|
||||
return;
|
||||
}
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
}
|
||||
|
||||
void ExitProcess(DeviceState &state) {
|
||||
state.os->KillThread(state.thisProcess->mainThread);
|
||||
}
|
||||
}
|
@ -2,9 +2,8 @@
|
||||
|
||||
#include "ipc.h"
|
||||
#include "../common.h"
|
||||
#include "switch/os.h"
|
||||
|
||||
namespace lightSwitch {
|
||||
namespace skyline {
|
||||
namespace constant::infoState {
|
||||
// 1.0.0+
|
||||
constexpr u8 AllowedCpuIdBitmask = 0x0;
|
||||
@ -37,79 +36,80 @@ namespace lightSwitch {
|
||||
constexpr u8 TotalMemoryUsedWithoutMmHeap = 0x16;
|
||||
};
|
||||
namespace kernel::svc {
|
||||
namespace structs {
|
||||
|
||||
}
|
||||
/**
|
||||
* @brief Set the process heap to a given size (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
|
||||
*/
|
||||
void SetHeapSize(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Set the process heap to a given size (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
|
||||
* @brief Query information about an address. Will always fetch the lowest page-aligned mapping that contains the provided address (https://switchbrew.org/wiki/SVC#svcQueryMemory)
|
||||
*/
|
||||
void SetHeapSize(device_state &state);
|
||||
void QueryMemory(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Query information about an address. Will always fetch the lowest page-aligned mapping that contains the provided address (https://switchbrew.org/wiki/SVC#svcQueryMemory)
|
||||
* @brief Exits the current process (https://switchbrew.org/wiki/SVC#svcExitProcess)
|
||||
*/
|
||||
void QueryMemory(device_state &state);
|
||||
void ExitProcess(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Exits the current process (https://switchbrew.org/wiki/SVC#svcExitProcess)
|
||||
* @brief Create a thread in the current process (https://switchbrew.org/wiki/SVC#svcCreateThread)
|
||||
*/
|
||||
void ExitProcess(device_state &state);
|
||||
void CreateThread(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Create a thread in the current process (https://switchbrew.org/wiki/SVC#svcCreateThread)
|
||||
* @brief Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#svcStartThread)
|
||||
*/
|
||||
void CreateThread(device_state &state);
|
||||
void StartThread(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#svcStartThread)
|
||||
* @brief Exits the current thread (https://switchbrew.org/wiki/SVC#svcExitThread)
|
||||
*/
|
||||
void StartThread(device_state &state);
|
||||
void ExitThread(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Exits the current thread (https://switchbrew.org/wiki/SVC#svcExitThread)
|
||||
* @brief Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcGetThreadPriority)
|
||||
*/
|
||||
void ExitThread(device_state &state);
|
||||
void GetThreadPriority(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcGetThreadPriority)
|
||||
* @brief Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcSetThreadPriority)
|
||||
*/
|
||||
void GetThreadPriority(device_state &state);
|
||||
void SetThreadPriority(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcSetThreadPriority)
|
||||
* @brief Closes the specified handle
|
||||
*/
|
||||
void SetThreadPriority(device_state &state);
|
||||
void CloseHandle(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Closes the specified handle
|
||||
* @brief Stalls a thread till a KSyncObject signals or the timeout has ended (https://switchbrew.org/wiki/SVC#svcWaitSynchronization)
|
||||
*/
|
||||
void CloseHandle(device_state &state);
|
||||
void WaitSynchronization(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Connects to a named IPC port
|
||||
* @brief Connects to a named IPC port
|
||||
*/
|
||||
void ConnectToNamedPort(device_state &state);
|
||||
void ConnectToNamedPort(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Send a synchronous IPC request to a service
|
||||
* @brief Send a synchronous IPC request to a service
|
||||
*/
|
||||
void SendSyncRequest(device_state &state);
|
||||
void SendSyncRequest(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Outputs a debug string
|
||||
* @brief Outputs a debug string
|
||||
*/
|
||||
void OutputDebugString(device_state &state);
|
||||
void OutputDebugString(DeviceState &state);
|
||||
|
||||
/**
|
||||
* Retrieves a piece of information (https://switchbrew.org/wiki/SVC#svcGetInfo)
|
||||
* @brief Retrieves a piece of information (https://switchbrew.org/wiki/SVC#svcGetInfo)
|
||||
*/
|
||||
void GetInfo(device_state &state);
|
||||
void GetInfo(DeviceState &state);
|
||||
|
||||
/**
|
||||
* The SVC Table maps all SVCs to their corresponding functions
|
||||
* @brief The SVC Table maps all SVCs to their corresponding functions
|
||||
*/
|
||||
void static (*svcTable[0x80])(device_state &) = {
|
||||
void static (*SvcTable[0x80])(DeviceState &) = {
|
||||
nullptr, // 0x00 (Does not exist)
|
||||
SetHeapSize, // 0x01
|
||||
nullptr, // 0x02
|
||||
@ -134,7 +134,7 @@ namespace lightSwitch {
|
||||
nullptr, // 0x15
|
||||
CloseHandle, // 0x16
|
||||
nullptr, // 0x17
|
||||
nullptr, // 0x18
|
||||
WaitSynchronization, // 0x18
|
||||
nullptr, // 0x19
|
||||
nullptr, // 0x1a
|
||||
nullptr, // 0x1b
|
31
app/src/main/cpp/skyline/kernel/types/KObject.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../common.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
* @brief These types are used to perform runtime evaluation of a kernel object's type when converting from base class
|
||||
*/
|
||||
enum class KType {
|
||||
KThread, KProcess, KSharedMemory, KPrivateMemory, KSession
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A base class that all Kernel objects have to derive from
|
||||
*/
|
||||
class KObject {
|
||||
public:
|
||||
handle_t handle; //!< The handle of this KObject
|
||||
pid_t ownerPid; //!< The pid of the process owning this object
|
||||
const DeviceState &state; //!< The state of the device
|
||||
KType handleType; //!< The type of this object
|
||||
|
||||
/**
|
||||
* @param handle The handle of the object in the handle table
|
||||
* @param ownerPid The PID of the process which owns this
|
||||
* @param state The state of the device
|
||||
* @param handleType The type of the object
|
||||
*/
|
||||
KObject(handle_t handle, pid_t ownerPid, const DeviceState &state, KType handleType) : handle(handle), ownerPid(ownerPid), state(state), handleType(handleType) {}
|
||||
};
|
||||
}
|
77
app/src/main/cpp/skyline/kernel/types/KPrivateMemory.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "KPrivateMemory.h"
|
||||
#include "../../nce.h"
|
||||
#include "../../os.h"
|
||||
#include <android/sharedmem.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
u64 MapPrivateFunc(u64 dstAddress, u64 srcAddress, size_t size, u64 perms) {
|
||||
dstAddress = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(dstAddress), size, static_cast<int>(perms), MAP_PRIVATE | MAP_ANONYMOUS | ((dstAddress) ? MAP_FIXED : 0), -1, 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
if (srcAddress) {
|
||||
memcpy(reinterpret_cast<void *>(dstAddress), reinterpret_cast<const void *>(srcAddress), size);
|
||||
mprotect(reinterpret_cast<void *>(srcAddress), size, PROT_NONE);
|
||||
}
|
||||
return dstAddress;
|
||||
}
|
||||
|
||||
KPrivateMemory::KPrivateMemory(handle_t handle, pid_t pid, const DeviceState &state, u64 dstAddress, u64 srcAddress, size_t size, memory::Permission permission, const memory::Type type) : state(state), address(dstAddress), size(size), permission(permission), type(type), KObject(handle, pid, state, KType::KPrivateMemory) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = dstAddress;
|
||||
fregs.regs[1] = srcAddress;
|
||||
fregs.regs[2] = size;
|
||||
fregs.regs[3] = static_cast<u64>(permission.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapPrivateFunc), fregs, ownerPid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while mapping private region in child process");
|
||||
if (!this->address)
|
||||
this->address = fregs.regs[0];
|
||||
}
|
||||
|
||||
u64 UnmapPrivateFunc(u64 address, size_t size) {
|
||||
return static_cast<u64>(munmap(reinterpret_cast<void *>(address), size));
|
||||
}
|
||||
|
||||
u64 RemapPrivateFunc(u64 address, size_t oldSize, size_t size) {
|
||||
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), oldSize, size, 0));
|
||||
}
|
||||
|
||||
void KPrivateMemory::Resize(size_t newSize) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = newSize;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, ownerPid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while remapping private region in child process");
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
u64 UpdatePermissionPrivateFunc(u64 address, size_t size, u64 perms) {
|
||||
return static_cast<u64>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
|
||||
}
|
||||
|
||||
void KPrivateMemory::UpdatePermission(memory::Permission newPerms) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64>(newPerms.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, ownerPid);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
throw exception("An error occurred while updating private region's permissions in child process");
|
||||
permission = newPerms;
|
||||
}
|
||||
|
||||
memory::MemoryInfo KPrivateMemory::GetInfo() {
|
||||
memory::MemoryInfo info{};
|
||||
info.baseAddress = address;
|
||||
info.size = size;
|
||||
info.type = static_cast<u64>(type);
|
||||
info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0);
|
||||
info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0);
|
||||
info.perms = permission;
|
||||
info.ipcRefCount = ipcRefCount;
|
||||
info.deviceRefCount = deviceRefCount;
|
||||
return info;
|
||||
}
|
||||
};
|
55
app/src/main/cpp/skyline/kernel/types/KPrivateMemory.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../memory.h"
|
||||
#include "KObject.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
* KPrivateMemory is used to hold some amount of private memory
|
||||
*/
|
||||
class KPrivateMemory : public KObject {
|
||||
private:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
|
||||
public:
|
||||
u64 address; //!< The address of the allocated memory
|
||||
size_t size; //!< The size of the allocated memory
|
||||
u16 ipcRefCount{}; //!< The amount of reference to this memory for IPC
|
||||
u16 deviceRefCount{}; //!< The amount of reference to this memory for IPC
|
||||
memory::Permission permission; //!< The permissions for the allocated memory
|
||||
const memory::Type type; //!< The type of this memory allocation
|
||||
|
||||
/**
|
||||
* @brief Constructor of a private memory object
|
||||
* @param handle A handle to this object
|
||||
* @param pid The PID of the main thread
|
||||
* @param state The state of the device
|
||||
* @param dstAddress The address to map to (If NULL then an arbitrary address is picked)
|
||||
* @param srcAddress The address to map from (If NULL then no copy is performed)
|
||||
* @param size The size of the allocation
|
||||
* @param permission The permissions for the allocated memory
|
||||
*/
|
||||
KPrivateMemory(handle_t handle, pid_t pid, const DeviceState &state, u64 dstAddress, u64 srcAddress, size_t size, memory::Permission permission, const memory::Type type);
|
||||
|
||||
/**
|
||||
* @brief Remap a chunk of memory as to change the size occupied by it
|
||||
* @param address The address of the mapped memory
|
||||
* @param old_size The current size of the memory
|
||||
* @param size The new size of the memory
|
||||
*/
|
||||
void Resize(size_t newSize);
|
||||
|
||||
/**
|
||||
* @brief Updates the permissions of a chunk of mapped memory
|
||||
* @param perms The new permissions to be set for the memory
|
||||
*/
|
||||
void UpdatePermission(memory::Permission newPerms);
|
||||
|
||||
/**
|
||||
* @brief Returns a MemoryInfo object
|
||||
* @param pid The PID of the requesting process
|
||||
* @return A Memory::MemoryInfo struct based on attributes of the memory
|
||||
*/
|
||||
memory::MemoryInfo GetInfo();
|
||||
};
|
||||
}
|
97
app/src/main/cpp/skyline/kernel/types/KProcess.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
#include "KProcess.h"
|
||||
#include "../../nce.h"
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
KProcess::TlsPage::TlsPage(u64 address) : address(address) {}
|
||||
|
||||
u64 KProcess::TlsPage::ReserveSlot() {
|
||||
if (Full())
|
||||
throw exception("Trying to get TLS slot from full page");
|
||||
slot[index] = true;
|
||||
return Get(index++); // ++ on right will cause increment after evaluation of expression
|
||||
}
|
||||
|
||||
u64 KProcess::TlsPage::Get(u8 slotNo) {
|
||||
if (slotNo >= constant::TlsSlots)
|
||||
throw exception("TLS slot is out of range");
|
||||
return address + (constant::TlsSlotSize * slotNo);
|
||||
}
|
||||
|
||||
bool KProcess::TlsPage::Full() {
|
||||
return slot[constant::TlsSlots - 1];
|
||||
}
|
||||
|
||||
u64 KProcess::GetTlsSlot(bool init) {
|
||||
if (!init)
|
||||
for (auto &tlsPage: tlsPages) {
|
||||
if (!tlsPage->Full())
|
||||
return tlsPage->ReserveSlot();
|
||||
}
|
||||
auto tlsMem = NewHandle<KPrivateMemory>(0, 0, PAGE_SIZE, memory::Permission(true, true, false), memory::Type::ThreadLocal);
|
||||
memoryMap[tlsMem->address] = tlsMem;
|
||||
tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address));
|
||||
auto &tlsPage = tlsPages.back();
|
||||
if (init)
|
||||
tlsPage->ReserveSlot(); // User-mode exception handling
|
||||
return tlsPage->ReserveSlot();
|
||||
}
|
||||
|
||||
KProcess::KProcess(handle_t handle, pid_t pid, const DeviceState &state, u64 entryPoint, u64 stackBase, u64 stackSize) : mainThread(pid), mainThreadStackSz(stackSize), KSyncObject(handle, pid, state, KType::KProcess) {
|
||||
state.nce->WaitRdy(pid);
|
||||
threadMap[mainThread] = NewHandle<KThread>(pid, entryPoint, 0, stackBase + stackSize, GetTlsSlot(true), constant::DefaultPriority, this);
|
||||
MapPrivateRegion(0, constant::DefHeapSize, {true, true, true}, memory::Type::Heap, memory::Region::Heap);
|
||||
for (auto ®ion : state.nce->memoryRegionMap)
|
||||
region.second->InitiateProcess(pid);
|
||||
memFd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
|
||||
if (memFd == -1)
|
||||
throw exception(fmt::format("Cannot open file descriptor to /proc/{}/mem", pid));
|
||||
}
|
||||
|
||||
KProcess::~KProcess() {
|
||||
close(memFd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function executed by all child threads after cloning
|
||||
*/
|
||||
int ExecuteChild(void *) {
|
||||
ptrace(PTRACE_TRACEME);
|
||||
asm volatile("Brk #0xFF"); // BRK #constant::brkRdy (So we know when the thread/process is ready)
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 CreateThreadFunc(u64 stackTop) {
|
||||
pid_t pid = clone(&ExecuteChild, reinterpret_cast<void *>(stackTop), CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM, nullptr); // NOLINT(hicpp-signed-bitwise)
|
||||
return static_cast<u64>(pid);
|
||||
}
|
||||
|
||||
std::shared_ptr<KThread> KProcess::CreateThread(u64 entryPoint, u64 entryArg, u64 stackTop, u8 priority) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = entryPoint;
|
||||
fregs.regs[1] = stackTop;
|
||||
state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, mainThread);
|
||||
auto pid = static_cast<pid_t>(fregs.regs[0]);
|
||||
if (pid == -1)
|
||||
throw exception(fmt::format("Cannot create thread: Address: {}, Stack Top: {}", entryPoint, stackTop));
|
||||
threadMap[pid] = NewHandle<KThread>(pid, entryPoint, entryArg, stackTop, GetTlsSlot(false), priority, this);
|
||||
return threadMap[pid];
|
||||
}
|
||||
|
||||
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
|
||||
pread64(memFd, destination, size, offset);
|
||||
}
|
||||
|
||||
void KProcess::WriteMemory(void *source, u64 offset, size_t size) const {
|
||||
pwrite64(memFd, source, size, offset);
|
||||
}
|
||||
|
||||
std::shared_ptr<KPrivateMemory> KProcess::MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region) {
|
||||
auto item = NewHandle<KPrivateMemory>(address, 0, size, perms, type);
|
||||
memoryMap[item->address] = item;
|
||||
memoryRegionMap[region] = item;
|
||||
return item;
|
||||
}
|
||||
}
|
193
app/src/main/cpp/skyline/kernel/types/KProcess.h
Normal file
@ -0,0 +1,193 @@
|
||||
#pragma once
|
||||
|
||||
#include "KThread.h"
|
||||
#include "KPrivateMemory.h"
|
||||
#include "KSharedMemory.h"
|
||||
#include "KSession.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
* @brief The KProcess class is responsible for holding the state of a process
|
||||
*/
|
||||
class KProcess : public KSyncObject {
|
||||
private:
|
||||
/**
|
||||
* @brief This class holds a single TLS page's status
|
||||
* @details tls_page_t holds the status of a single TLS page (A page is 4096 bytes on ARMv8).
|
||||
* Each TLS page has 8 slots, each 0x200 (512) bytes in size.
|
||||
* The first slot of the first page is reserved for user-mode exception handling.
|
||||
* Read more about TLS here: https://switchbrew.org/wiki/Thread_Local_Storage
|
||||
*/
|
||||
struct TlsPage {
|
||||
u64 address; //!< The address of the page allocated for TLS
|
||||
u8 index = 0; //!< The slots are assigned sequentially, this holds the index of the last TLS slot reserved
|
||||
bool slot[constant::TlsSlots]{0}; //!< An array of booleans denoting which TLS slots are reserved
|
||||
|
||||
/**
|
||||
* @param address The address of the allocated page
|
||||
*/
|
||||
TlsPage(u64 address);
|
||||
|
||||
/**
|
||||
* @brief Reserves a single 0x200 byte TLS slot
|
||||
* @return The address of the reserved slot
|
||||
*/
|
||||
u64 ReserveSlot();
|
||||
|
||||
/**
|
||||
* @brief Returns the address of a particular slot
|
||||
* @param slotNo The number of the slot to be returned
|
||||
* @return The address of the specified slot
|
||||
*/
|
||||
u64 Get(u8 slotNo);
|
||||
|
||||
/**
|
||||
* @brief Returns boolean on if the TLS page has free slots or not
|
||||
* @return If the whole page is full or not
|
||||
*/
|
||||
bool Full();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Returns a TLS slot from an arbitrary TLS page
|
||||
* @param init If this initializes the first page (As the first TLS slot is reserved)
|
||||
* @return The address of a free TLS slot
|
||||
*/
|
||||
u64 GetTlsSlot(bool init);
|
||||
|
||||
int memFd; //!< The file descriptor to the memory of the process
|
||||
|
||||
public:
|
||||
enum class ProcessStatus { Created, CreatedAttached, Started, Crashed, StartedAttached, Exiting, Exited, DebugSuspended } status = ProcessStatus::Created; //!< The state of the process
|
||||
handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle
|
||||
pid_t mainThread; //!< The PID of the main thread
|
||||
size_t mainThreadStackSz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se)
|
||||
std::map<u64, std::shared_ptr<KPrivateMemory>> memoryMap; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory, used to keep track of KPrivateMemory instances
|
||||
std::map<memory::Region, std::shared_ptr<KPrivateMemory>> memoryRegionMap; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory
|
||||
std::map<handle_t, std::shared_ptr<KObject>> handleTable; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
|
||||
std::map<pid_t, std::shared_ptr<KThread>> threadMap; //!< A mapping from a PID to it's corresponding KThread object
|
||||
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages
|
||||
|
||||
/**
|
||||
* @brief Creates a KThread object for the main thread and opens the process's memory file
|
||||
* @param handle A handle to the process, this isn't used if the kernel creates the process
|
||||
* @param pid The PID of the main thread
|
||||
* @param state The state of the device
|
||||
* @param entryPoint The address to start execution at
|
||||
* @param stackBase The base of the stack
|
||||
* @param stackSize The size of the stack
|
||||
*/
|
||||
KProcess(handle_t handle, pid_t pid, const DeviceState &state, u64 entryPoint, u64 stackBase, u64 stackSize);
|
||||
|
||||
/**
|
||||
* Close the file descriptor to the process's memory
|
||||
*/
|
||||
~KProcess();
|
||||
|
||||
/**
|
||||
* @brief Create a thread in this process
|
||||
* @param entryPoint The address of the initial function
|
||||
* @param entryArg An argument to the function
|
||||
* @param stackTop The top of the stack
|
||||
* @param priority The priority of the thread
|
||||
* @return An instance of KThread class for the corresponding thread
|
||||
*/
|
||||
std::shared_ptr<KThread> CreateThread(u64 entryPoint, u64 entryArg, u64 stackTop, u8 priority);
|
||||
|
||||
/**
|
||||
* @brief Returns an object from process memory
|
||||
* @tparam Type The type of the object to be read
|
||||
* @param address The address of the object
|
||||
* @return An object of type T with read data
|
||||
*/
|
||||
template<typename Type>
|
||||
Type ReadMemory(u64 address) const {
|
||||
Type item{};
|
||||
ReadMemory(&item, address, sizeof(Type));
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes an object to process memory
|
||||
* @tparam Type The type of the object to be written
|
||||
* @param item The object to write
|
||||
* @param address The address of the object
|
||||
*/
|
||||
template<typename Type>
|
||||
void WriteMemory(Type &item, u64 address) const {
|
||||
WriteMemory(&item, address, sizeof(Type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read data from the process's memory
|
||||
* @param destination The address to the location where the process memory is written
|
||||
* @param offset The address to read from in process memory
|
||||
* @param size The amount of memory to be read
|
||||
*/
|
||||
void ReadMemory(void *destination, u64 offset, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief Write to the process's memory
|
||||
* @param source The address of where the data to be written is present
|
||||
* @param offset The address to write to in process memory
|
||||
* @param size The amount of memory to be written
|
||||
*/
|
||||
void WriteMemory(void *source, u64 offset, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief Map a chunk of process local memory (private memory)
|
||||
* @param address The address to map to (Can be 0 if address doesn't matter)
|
||||
* @param size The size of the chunk of memory
|
||||
* @param perms The permissions of the memory
|
||||
* @param type The type of the memory
|
||||
* @param region The specific region this memory is mapped for
|
||||
* @return The address of the mapped chunk (Use when address is 0)
|
||||
*/
|
||||
std::shared_ptr<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region);
|
||||
|
||||
/**
|
||||
* @brief Creates a new handle to a KObject and adds it to the process handle_table
|
||||
* @tparam objectClass The class of the kernel object to create
|
||||
* @param args The arguments for the kernel object except handle, pid and state
|
||||
* @return A shared pointer to the corresponding object
|
||||
*/
|
||||
template<typename objectClass, typename ...objectArgs>
|
||||
std::shared_ptr<objectClass> NewHandle(objectArgs... args) {
|
||||
std::shared_ptr<objectClass> item = std::make_shared<objectClass>(handleIndex, mainThread, state, args...);
|
||||
handleTable[handleIndex++] = std::static_pointer_cast<KObject>(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the underlying kernel object for a handle
|
||||
* @tparam objectClass The class of the kernel object present in the handle
|
||||
* @param handle The handle of the object
|
||||
* @return A shared pointer to the object
|
||||
*/
|
||||
template<typename objectClass>
|
||||
std::shared_ptr<objectClass> GetHandle(handle_t handle) {
|
||||
KType objectType;
|
||||
if(std::is_same<objectClass, KThread>::value)
|
||||
objectType = KType::KThread;
|
||||
else if(std::is_same<objectClass, KProcess>::value)
|
||||
objectType = KType::KProcess;
|
||||
else if(std::is_same<objectClass, KSharedMemory>::value)
|
||||
objectType = KType::KSharedMemory;
|
||||
else if(std::is_same<objectClass, KPrivateMemory>::value)
|
||||
objectType = KType::KPrivateMemory;
|
||||
else if(std::is_same<objectClass, KSession>::value)
|
||||
objectType = KType::KSession;
|
||||
else
|
||||
throw exception("KProcess::GetHandle couldn't determine object type");
|
||||
try {
|
||||
auto &item = handleTable.at(handle);
|
||||
if (item->handleType == objectType)
|
||||
return std::static_pointer_cast<objectClass>(item);
|
||||
else
|
||||
throw exception(fmt::format("Tried to get kernel object (0x{:X}) with different type: {} when object is {}", handle, objectType, item->handleType));
|
||||
} catch (std::out_of_range) {
|
||||
throw exception(fmt::format("GetHandle was called with invalid handle: 0x{:X}", handle));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
39
app/src/main/cpp/skyline/kernel/types/KSession.h
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../common.h"
|
||||
#include "../services/base_service.h"
|
||||
#include "KSyncObject.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
* @brief KService holds a reference to a service, this is equivalent to KClientSession
|
||||
*/
|
||||
class KSession : public KSyncObject {
|
||||
public:
|
||||
const std::shared_ptr<service::BaseService> serviceObject; //!< A shared pointer to the service class
|
||||
std::unordered_map<handle_t, std::shared_ptr<service::BaseService>> domainTable; //!< This maps from a virtual handle to it's service
|
||||
handle_t handleIndex = constant::BaseVirtualHandleIndex;
|
||||
const service::Service serviceType; //!< The type of the service
|
||||
enum class ServiceStatus { Open, Closed } serviceStatus = ServiceStatus::Open; //!< If the session is open or closed
|
||||
bool isDomain{}; //!< Holds if this is a domain session or not
|
||||
|
||||
/**
|
||||
* @param handle A handle to this object
|
||||
* @param pid The PID of the main thread
|
||||
* @param state The state of the device
|
||||
* @param serviceObject A shared pointer to the service class
|
||||
* @param serviceType The type of the service
|
||||
*/
|
||||
KSession(handle_t handle, pid_t pid, const DeviceState &state, std::shared_ptr<service::BaseService> &serviceObject, const service::Service &serviceType) : serviceObject(serviceObject), serviceType(serviceType), KSyncObject(handle, pid, state, KType::KSession) {}
|
||||
|
||||
/**
|
||||
* This converts this session into a domain session (https://switchbrew.org/wiki/IPC_Marshalling#Domains)
|
||||
* @return The virtual handle of this service in the domain
|
||||
*/
|
||||
handle_t ConvertDomain() {
|
||||
isDomain = true;
|
||||
domainTable[handleIndex] = serviceObject;
|
||||
return handleIndex++;
|
||||
}
|
||||
};
|
||||
}
|
@ -1,36 +1,41 @@
|
||||
#include "KSharedMemory.h"
|
||||
#include "../../nce.h"
|
||||
#include "../../os.h"
|
||||
#include <android/sharedmem.h>
|
||||
//#include <android/sharedmem.h>
|
||||
#include <cutils/ashmem.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
namespace skyline::kernel::type {
|
||||
u64 MapFunc(u64 address, size_t size, u64 perms, u64 fd) {
|
||||
return reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_SHARED | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
}
|
||||
|
||||
KSharedMemory::KSharedMemory(const device_state &state, size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, Memory::Type type, handle_t handle, pid_t owner_pid) : state(state), size(size), local_permission(local_permission), remote_permission(remote_permission), type(type), owner_pid(owner_pid), KObject(handle, KObjectType::KSharedMemory) {
|
||||
fd = ASharedMemory_create("", size);
|
||||
KSharedMemory::KSharedMemory(handle_t handle, pid_t pid, const DeviceState &state, size_t size, const memory::Permission localPermission, const memory::Permission remotePermission, memory::Type type) : size(size), localPermission(localPermission), remotePermission(remotePermission), type(type), KObject(handle, pid, state, KType::KSharedMemory) {
|
||||
fd = open(ASHMEM_NAME_DEF, O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
|
||||
if (fd < 0)
|
||||
throw exception(fmt::format("An error occurred while opening {}: {}", ASHMEM_NAME_DEF, fd));
|
||||
if (ioctl(fd, ASHMEM_SET_SIZE, size) < 0) // NOLINT(hicpp-signed-bitwise)
|
||||
throw exception(fmt::format("An error occurred while setting shared memory size: {}", size));
|
||||
}
|
||||
|
||||
void KSharedMemory::Map(u64 address) {
|
||||
this->address = address;
|
||||
for (auto process : state.os->process_vec) {
|
||||
for (auto process : state.os->processVec) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = this->address;
|
||||
fregs.regs[1] = size;
|
||||
if (process == owner_pid)
|
||||
fregs.regs[2] = static_cast<u64 >(local_permission.get());
|
||||
if (process == ownerPid)
|
||||
fregs.regs[2] = static_cast<u64 >(localPermission.Get());
|
||||
else
|
||||
fregs.regs[2] = static_cast<u64>(remote_permission.get());
|
||||
fregs.regs[2] = static_cast<u64>(remotePermission.Get());
|
||||
fregs.regs[3] = static_cast<u64>(fd);
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapFunc), fregs, process);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while mapping shared region in child process");
|
||||
if (!this->address) this->address = fregs.regs[0];
|
||||
}
|
||||
this->address = MapFunc(this->address, size, static_cast<u64>(owner_pid ? remote_permission.get() : local_permission.get()), static_cast<u64>(fd));
|
||||
this->address = MapFunc(this->address, size, static_cast<u64>(ownerPid ? remotePermission.Get() : localPermission.Get()), static_cast<u64>(fd));
|
||||
if (this->address == reinterpret_cast<u64>(MAP_FAILED)) // NOLINT(hicpp-signed-bitwise)
|
||||
throw exception(fmt::format("An occurred while mapping shared region: {}", strerror(errno)));
|
||||
}
|
||||
@ -40,7 +45,7 @@ namespace lightSwitch::kernel::type {
|
||||
}
|
||||
|
||||
KSharedMemory::~KSharedMemory() {
|
||||
for (auto process : state.os->process_vec) {
|
||||
for (auto process : state.os->processVec) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
@ -50,70 +55,70 @@ namespace lightSwitch::kernel::type {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
u64 RemapFunc(u64 address, size_t old_size, size_t size) {
|
||||
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
|
||||
u64 RemapFunc(u64 address, size_t oldSize, size_t size) {
|
||||
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), oldSize, size, 0));
|
||||
}
|
||||
|
||||
void KSharedMemory::Resize(size_t new_size) {
|
||||
for (auto process : state.os->process_vec) {
|
||||
void KSharedMemory::Resize(size_t newSize) {
|
||||
for (auto process : state.os->processVec) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = new_size;
|
||||
fregs.regs[2] = newSize;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapFunc), fregs, process);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while remapping shared region in child process");
|
||||
}
|
||||
if (RemapFunc(address, size, new_size) == reinterpret_cast<u64>(MAP_FAILED))
|
||||
if (RemapFunc(address, size, newSize) == reinterpret_cast<u64>(MAP_FAILED))
|
||||
throw exception(fmt::format("An occurred while remapping shared region: {}", strerror(errno)));
|
||||
size = new_size;
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
u64 UpdatePermissionFunc(u64 address, size_t size, u64 perms) {
|
||||
return static_cast<u64>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
|
||||
}
|
||||
|
||||
void KSharedMemory::UpdatePermission(bool local, Memory::Permission new_perms) {
|
||||
for (auto process : state.os->process_vec) {
|
||||
if ((local && process == owner_pid) || (!local && process != owner_pid)) {
|
||||
void KSharedMemory::UpdatePermission(bool local, memory::Permission newPerms) {
|
||||
for (auto process : state.os->processVec) {
|
||||
if ((local && process == ownerPid) || (!local && process != ownerPid)) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64>(new_perms.get());
|
||||
fregs.regs[2] = static_cast<u64>(newPerms.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionFunc), fregs, process);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
throw exception("An error occurred while updating shared region's permissions in child process");
|
||||
}
|
||||
}
|
||||
if ((local && owner_pid == 0) || (!local && owner_pid != 0))
|
||||
if (mprotect(reinterpret_cast<void *>(address), size, new_perms.get()) == -1)
|
||||
if ((local && ownerPid == 0) || (!local && ownerPid != 0))
|
||||
if (mprotect(reinterpret_cast<void *>(address), size, newPerms.Get()) == -1)
|
||||
throw exception(fmt::format("An occurred while updating shared region's permissions: {}", strerror(errno)));
|
||||
if (local)
|
||||
local_permission = new_perms;
|
||||
localPermission = newPerms;
|
||||
else
|
||||
remote_permission = new_perms;
|
||||
remotePermission = newPerms;
|
||||
}
|
||||
|
||||
void KSharedMemory::InitiateProcess(pid_t pid) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64>(remote_permission.get());
|
||||
fregs.regs[2] = static_cast<u64>(remotePermission.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionFunc), fregs, pid);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
throw exception("An error occurred while setting shared region's permissions in child process");
|
||||
}
|
||||
|
||||
Memory::MemoryInfo KSharedMemory::GetInfo(pid_t pid) {
|
||||
Memory::MemoryInfo info{};
|
||||
info.base_address = address;
|
||||
memory::MemoryInfo KSharedMemory::GetInfo(pid_t pid) {
|
||||
memory::MemoryInfo info{};
|
||||
info.baseAddress = address;
|
||||
info.size = size;
|
||||
info.type = static_cast<u64>(type);
|
||||
info.memory_attribute.IsIpcLocked = (info.ipc_ref_count > 0);
|
||||
info.memory_attribute.IsDeviceShared = (info.device_ref_count > 0);
|
||||
info.perms = (pid == owner_pid) ? local_permission : remote_permission;
|
||||
info.ipc_ref_count = ipc_ref_count;
|
||||
info.device_ref_count = device_ref_count;
|
||||
info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0);
|
||||
info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0);
|
||||
info.perms = (pid == ownerPid) ? localPermission : remotePermission;
|
||||
info.ipcRefCount = ipcRefCount;
|
||||
info.deviceRefCount = deviceRefCount;
|
||||
return info;
|
||||
}
|
||||
};
|
69
app/src/main/cpp/skyline/kernel/types/KSharedMemory.h
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../memory.h"
|
||||
#include "KObject.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
* @brief KSharedMemory is used to hold a particular amount of shared memory
|
||||
*/
|
||||
class KSharedMemory : public KObject {
|
||||
private:
|
||||
int fd; //!< A file descriptor to the underlying shared memory
|
||||
|
||||
public:
|
||||
u64 address{}; //!< The address of the allocated memory
|
||||
size_t size; //!< The size of the allocated memory
|
||||
u16 ipcRefCount{}; //!< The amount of reference to this memory for IPC
|
||||
u16 deviceRefCount{}; //!< The amount of reference to this memory for IPC
|
||||
memory::Permission localPermission; //!< The permission for the owner process
|
||||
memory::Permission remotePermission; //!< The permission of any process except the owner process
|
||||
memory::Type type; //!< The type of this memory allocation
|
||||
|
||||
/**
|
||||
* @param handle A handle to this object
|
||||
* @param pid The PID of the main thread
|
||||
* @param state The state of the device
|
||||
* @param size The size of the allocation
|
||||
* @param localPermission The permission of the owner process
|
||||
* @param remotePermission The permission of any process except the owner process
|
||||
*/
|
||||
KSharedMemory(handle_t handle, pid_t pid, const DeviceState &state, size_t size, const memory::Permission localPermission, const memory::Permission remotePermission, memory::Type type);
|
||||
|
||||
/**
|
||||
* @brief Maps the shared memory at an address
|
||||
* @param address The address to map to (If NULL an arbitrary address is picked)
|
||||
*/
|
||||
void Map(u64 address);
|
||||
|
||||
/**
|
||||
* @brief Destructor of shared memory, it deallocates the memory from all processes
|
||||
*/
|
||||
~KSharedMemory();
|
||||
|
||||
/**
|
||||
* @brief Resize a chunk of memory as to change the size occupied by it
|
||||
* @param newSize The new size of the memory
|
||||
*/
|
||||
void Resize(size_t newSize);
|
||||
|
||||
/**
|
||||
* Updates the permissions of a chunk of mapped memory
|
||||
* @param local If true change local permissions else change remote permissions
|
||||
* @param perms The new permissions to be set for the memory
|
||||
*/
|
||||
void UpdatePermission(bool local, memory::Permission newPerms);
|
||||
|
||||
/**
|
||||
* Initiates the instance of shared memory in a particular process
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void InitiateProcess(pid_t pid);
|
||||
|
||||
/**
|
||||
* @param pid The PID of the requesting process
|
||||
* @return A Memory::MemoryInfo struct based on attributes of the memory
|
||||
*/
|
||||
memory::MemoryInfo GetInfo(pid_t pid);
|
||||
};
|
||||
}
|
21
app/src/main/cpp/skyline/kernel/types/KSyncObject.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "KSyncObject.h"
|
||||
#include "../../os.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
KSyncObject::KSyncObject(skyline::handle_t handle, pid_t pid, const skyline::DeviceState &state, skyline::kernel::type::KType type) : KObject(handle, pid, state, type) {}
|
||||
|
||||
void KSyncObject::Signal() {
|
||||
for (auto&[tid, process] : state.os->threadMap) {
|
||||
auto &thread = process->threadMap.at(tid);
|
||||
if (thread->status == type::KThread::ThreadStatus::Waiting) {
|
||||
for (auto &waitHandle : thread->waitHandles) {
|
||||
if (handle == waitHandle) {
|
||||
thread->status = type::KThread::ThreadStatus::Runnable;
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success, thread->pid);
|
||||
state.nce->SetRegister(Wreg::W1, handle, thread->pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
app/src/main/cpp/skyline/kernel/types/KSyncObject.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../common.h"
|
||||
#include "KObject.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
* @brief KSyncObject holds the state of a waitable object
|
||||
*/
|
||||
class KSyncObject : public KObject {
|
||||
public:
|
||||
/**
|
||||
* @param handle The handle of the object in the handle table
|
||||
* @param pid The PID of the main thread
|
||||
* @param state The state of the device
|
||||
* @param type The type of the object
|
||||
*/
|
||||
KSyncObject(skyline::handle_t handle, pid_t pid, const DeviceState &state, skyline::kernel::type::KType type);
|
||||
|
||||
// TODO: Rewrite this so that we store list of waiting threads instead
|
||||
/**
|
||||
* @brief A function for calling when a particular KSyncObject is signalled
|
||||
*/
|
||||
void Signal();
|
||||
};
|
||||
}
|
28
app/src/main/cpp/skyline/kernel/types/KThread.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include <sys/resource.h>
|
||||
#include "KThread.h"
|
||||
#include "KProcess.h"
|
||||
#include "../../nce.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
KThread::KThread(handle_t handle, pid_t parent_pid, const DeviceState &state, pid_t self_pid, u64 entryPoint, u64 entryArg, u64 stackTop, u64 tls, u8 priority, KProcess *parent) : pid(self_pid), entryPoint(entryPoint), entryArg(entryArg), stackTop(stackTop), tls(tls), priority(priority), parent(parent), KSyncObject(handle, parent_pid, state, KType::KThread) {
|
||||
UpdatePriority(priority);
|
||||
}
|
||||
|
||||
KThread::~KThread() {
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
|
||||
void KThread::Start() {
|
||||
if (pid == parent->mainThread)
|
||||
parent->status = KProcess::ProcessStatus::Started;
|
||||
status = ThreadStatus::Running;
|
||||
state.nce->StartProcess(entryPoint, entryArg, stackTop, handle, pid);
|
||||
}
|
||||
|
||||
void KThread::UpdatePriority(u8 priority) {
|
||||
this->priority = priority;
|
||||
auto liPriority = static_cast<int8_t>(constant::PriorityAn.first + ((static_cast<float>(constant::PriorityAn.second - constant::PriorityAn.first) / static_cast<float>(constant::PriorityNin.second - constant::PriorityNin.first)) * (static_cast<float>(priority) - constant::PriorityNin.first))); // Resize range PriorityNin (Nintendo Priority) to PriorityAn (Android Priority)
|
||||
if (setpriority(PRIO_PROCESS, static_cast<id_t>(pid), liPriority) == -1)
|
||||
throw exception(fmt::format("Couldn't set process priority to {} for PID: {}", liPriority, pid));
|
||||
}
|
||||
}
|
55
app/src/main/cpp/skyline/kernel/types/KThread.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "KSyncObject.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
* @brief KThread class is responsible for holding the state of a thread
|
||||
*/
|
||||
class KThread : public KSyncObject {
|
||||
private:
|
||||
KProcess *parent; //!< The parent process of this thread
|
||||
u64 entryPoint; //!< The address to start execution at
|
||||
u64 entryArg; //!< An argument to pass to the process on entry
|
||||
|
||||
public:
|
||||
enum class ThreadStatus { Created, Running, Waiting, Runnable } status = ThreadStatus::Created; //!< The state of the thread
|
||||
std::vector<handle_t> waitHandles; //!< A vector holding handles this thread is waiting for
|
||||
u64 timeoutEnd{}; //!< The time when a svcWaitSynchronization
|
||||
pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level])
|
||||
u64 stackTop; //!< The top of the stack (Where it starts growing downwards from)
|
||||
u64 tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread
|
||||
u8 priority; //!< Hold the priority of a thread in Nintendo format
|
||||
|
||||
/**
|
||||
* @param handle The handle of the current thread
|
||||
* @param parent_pid The PID of the main thread
|
||||
* @param state The state of the device
|
||||
* @param self_pid The PID of this thread
|
||||
* @param entryPoint The address to start execution at
|
||||
* @param entryArg An argument to pass to the process on entry
|
||||
* @param stackTop The top of the stack
|
||||
* @param tls The address of the TLS slot assigned
|
||||
* @param priority The priority of the thread in Nintendo format
|
||||
* @param parent The parent process of this thread
|
||||
*/
|
||||
KThread(handle_t handle, pid_t parent_pid, const DeviceState &state, pid_t self_pid, u64 entryPoint, u64 entryArg, u64 stackTop, u64 tls, u8 priority, KProcess *parent);
|
||||
|
||||
/**
|
||||
* @brief Kills the thread and deallocates the memory allocated for stack.
|
||||
*/
|
||||
~KThread();
|
||||
|
||||
/**
|
||||
* @brief Starts the current thread
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* @brief Update the priority level for the process.
|
||||
* @details Set the priority of the current thread to `priority` using setpriority [https://linux.die.net/man/3/setpriority]. We rescale the priority from Nintendo scale to that of Android.
|
||||
* @param priority The priority of the thread in Nintendo format
|
||||
*/
|
||||
void UpdatePriority(u8 priority);
|
||||
};
|
||||
}
|
@ -3,14 +3,14 @@
|
||||
#include <string>
|
||||
#include "../os.h"
|
||||
|
||||
namespace lightSwitch::loader {
|
||||
namespace skyline::loader {
|
||||
class Loader {
|
||||
protected:
|
||||
std::string file_path; //!< The path to the ROM file
|
||||
std::string filePath; //!< The path to the ROM file
|
||||
std::ifstream file; //!< An input stream from the file
|
||||
|
||||
/**
|
||||
* Read the file at a particular offset
|
||||
* @brief Read the file at a particular offset
|
||||
* @tparam T The type of object to write to
|
||||
* @param output The object to write to
|
||||
* @param offset The offset to read the file at
|
||||
@ -27,6 +27,6 @@ namespace lightSwitch::loader {
|
||||
/**
|
||||
* @param file_path_ The path to the ROM file
|
||||
*/
|
||||
Loader(std::string &file_path) : file_path(file_path), file(file_path, std::ios::binary | std::ios::beg) {}
|
||||
Loader(std::string &filePath) : filePath(filePath), file(filePath, std::ios::binary | std::ios::beg) {}
|
||||
};
|
||||
}
|
44
app/src/main/cpp/skyline/loader/nro.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include <vector>
|
||||
#include "nro.h"
|
||||
|
||||
namespace skyline::loader {
|
||||
NroLoader::NroLoader(std::string filePath, const DeviceState &state) : Loader(filePath) {
|
||||
NroHeader header{};
|
||||
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
|
||||
if (header.magic != constant::NroMagic)
|
||||
throw exception(fmt::format("Invalid NRO magic! 0x{0:X}", header.magic));
|
||||
|
||||
state.nce->MapSharedRegion(constant::BaseAddr, header.text.size, {true, true, true}, {true, true, true}, memory::Type::CodeStatic, memory::Region::Text); // R-X
|
||||
state.logger->Write(Logger::Debug, "Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr, header.text.size);
|
||||
|
||||
auto rodata = state.nce->MapSharedRegion(constant::BaseAddr + header.text.size, header.ro.size, {true, true, false}, {true, false, false}, memory::Type::CodeReadOnly, memory::Region::RoData); // R--
|
||||
state.logger->Write(Logger::Debug, "Successfully mapped region .ro @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + header.text.size, header.ro.size);
|
||||
|
||||
state.nce->MapSharedRegion(constant::BaseAddr + header.text.size + header.ro.size, header.data.size, {true, true, false}, {true, true, false}, memory::Type::CodeStatic, memory::Region::Data); // RW-
|
||||
state.logger->Write(Logger::Debug, "Successfully mapped region .data @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + header.text.size + header.ro.size, header.data.size);
|
||||
|
||||
state.nce->MapSharedRegion(constant::BaseAddr + header.text.size + header.ro.size + header.data.size, header.bssSize, {true, true, true}, {true, true, true}, memory::Type::CodeMutable, memory::Region::Bss); // RWX
|
||||
state.logger->Write(Logger::Debug, "Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + header.text.size + header.ro.size + header.data.size, header.bssSize);
|
||||
|
||||
ReadOffset(reinterpret_cast<u8 *>(constant::BaseAddr), header.text.offset, header.text.size);
|
||||
ReadOffset(reinterpret_cast<u8 *>(constant::BaseAddr + header.text.size), header.ro.offset, header.ro.size);
|
||||
ReadOffset(reinterpret_cast<u8 *>(constant::BaseAddr + header.text.size + header.ro.size), header.data.offset, header.data.size);
|
||||
|
||||
|
||||
// Replace SVC & MRS with BRK
|
||||
auto address = (u32 *) constant::BaseAddr + header.text.offset;
|
||||
size_t textSize = header.text.size / sizeof(u32);
|
||||
for (size_t iter = 0; iter < textSize; iter++) {
|
||||
auto instrSvc = reinterpret_cast<instr::Svc *>(address + iter);
|
||||
auto instrMrs = reinterpret_cast<instr::Mrs *>(address + iter);
|
||||
|
||||
if (instrSvc->Verify()) {
|
||||
instr::Brk brk(static_cast<u16>(instrSvc->value));
|
||||
address[iter] = *reinterpret_cast<u32 *>(&brk);
|
||||
} else if (instrMrs->Verify() && instrMrs->srcReg == constant::TpidrroEl0) {
|
||||
instr::Brk brk(static_cast<u16>(constant::SvcLast + 1 + instrMrs->dstReg));
|
||||
address[iter] = *reinterpret_cast<u32 *>(&brk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,14 +3,20 @@
|
||||
#include <cstdint>
|
||||
#include "loader.h"
|
||||
|
||||
namespace lightSwitch::loader {
|
||||
namespace skyline::loader {
|
||||
class NroLoader : public Loader {
|
||||
private:
|
||||
/**
|
||||
* @brief The structure of a single Segment descriptor in the NRO's header
|
||||
*/
|
||||
struct NroSegmentHeader {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
}; //!< The structure of a single Segment descriptor in the NRO's header
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct to read the header of an NRO directly
|
||||
*/
|
||||
struct NroHeader {
|
||||
u32 : 32;
|
||||
u32 mod_offset;
|
||||
@ -33,13 +39,13 @@ namespace lightSwitch::loader {
|
||||
NroSegmentHeader api_info;
|
||||
NroSegmentHeader dynstr;
|
||||
NroSegmentHeader dynsym;
|
||||
}; //!< A bit-field struct to read the header of an NRO directly
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param file_path The path to the ROM file
|
||||
* @param filePath The path to the ROM file
|
||||
* @param state The state of the device
|
||||
*/
|
||||
NroLoader(std::string file_path, const device_state &state);
|
||||
NroLoader(std::string filePath, const DeviceState &state);
|
||||
};
|
||||
}
|
@ -2,13 +2,13 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace lightSwitch::Memory {
|
||||
namespace skyline::memory {
|
||||
/**
|
||||
* The Permission struct holds the permission of a particular chunk of memory
|
||||
* @brief The Permission struct holds the permission of a particular chunk of memory
|
||||
*/
|
||||
struct Permission {
|
||||
/**
|
||||
* Initializes all values to false
|
||||
* @brief Constructor that initializes all permissions to false
|
||||
*/
|
||||
Permission() {
|
||||
r = 0;
|
||||
@ -28,19 +28,19 @@ namespace lightSwitch::Memory {
|
||||
};
|
||||
|
||||
/**
|
||||
* Equality operator between two Permission objects
|
||||
* @brief Equality operator between two Permission objects
|
||||
*/
|
||||
bool operator==(const Permission &rhs) const { return (this->r == rhs.r && this->w == rhs.w && this->x == rhs.x); };
|
||||
|
||||
/**
|
||||
* Inequality operator between two Permission objects
|
||||
* @brief Inequality operator between two Permission objects
|
||||
*/
|
||||
bool operator!=(const Permission &rhs) const { return !operator==(rhs); };
|
||||
|
||||
/**
|
||||
* @return The value of the permission struct in mmap(2) format
|
||||
*/
|
||||
int get() const {
|
||||
int Get() const {
|
||||
int perm = 0;
|
||||
if (r) perm |= PROT_READ;
|
||||
if (w) perm |= PROT_WRITE;
|
||||
@ -52,30 +52,33 @@ namespace lightSwitch::Memory {
|
||||
};
|
||||
|
||||
/**
|
||||
* This holds certain attributes of a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryAttribute
|
||||
* @brief This holds certain attributes of a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryAttribute
|
||||
*/
|
||||
struct MemoryAttribute {
|
||||
bool IsBorrowed : 1;
|
||||
bool IsIpcLocked : 1;
|
||||
bool IsDeviceShared : 1;
|
||||
bool IsUncached : 1;
|
||||
bool isBorrowed : 1;
|
||||
bool isIpcLocked : 1;
|
||||
bool isDeviceShared : 1;
|
||||
bool isUncached : 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
|
||||
* @brief This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
|
||||
*/
|
||||
struct MemoryInfo {
|
||||
u64 base_address : 64;
|
||||
u64 baseAddress : 64;
|
||||
u64 size : 64;
|
||||
u64 type : 64;
|
||||
MemoryAttribute memory_attribute;
|
||||
MemoryAttribute memoryAttribute;
|
||||
Permission perms;
|
||||
u32 ipc_ref_count : 32;
|
||||
u32 device_ref_count : 32;
|
||||
u32 ipcRefCount : 32;
|
||||
u32 deviceRefCount : 32;
|
||||
u32 : 32;
|
||||
};
|
||||
static_assert(sizeof(MemoryInfo) == 0x28);
|
||||
|
||||
/**
|
||||
* @brief These are specific markers for the type of a memory region
|
||||
*/
|
||||
enum class Type : u32 {
|
||||
Unmapped = 0x00000000,
|
||||
Io = 0x00002001,
|
||||
@ -102,9 +105,9 @@ namespace lightSwitch::Memory {
|
||||
};
|
||||
|
||||
/**
|
||||
* Memory Regions that are mapped by the kernel
|
||||
* @brief Memory Regions that are mapped by the kernel
|
||||
*/
|
||||
enum class Region {
|
||||
heap, text, rodata, data, bss
|
||||
Heap, Text, RoData, Data, Bss
|
||||
};
|
||||
}
|
209
app/src/main/cpp/skyline/nce.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
#include <sched.h>
|
||||
#include <linux/uio.h>
|
||||
#include <linux/elf.h>
|
||||
#include "os.h"
|
||||
#include "nce.h"
|
||||
|
||||
extern bool Halt;
|
||||
|
||||
namespace skyline {
|
||||
void NCE::ReadRegisters(user_pt_regs ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, sizeof(registers)};
|
||||
long status = ptrace(PTRACE_GETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
|
||||
if (status == -1)
|
||||
throw exception(fmt::format("Cannot read registers, PID: {}, Error: {}", pid, strerror(errno)));
|
||||
}
|
||||
|
||||
void NCE::WriteRegisters(user_pt_regs ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, sizeof(registers)};
|
||||
long status = ptrace(PTRACE_SETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
|
||||
if (status == -1)
|
||||
throw exception(fmt::format("Cannot write registers, PID: {}, Error: {}", pid, strerror(errno)));
|
||||
}
|
||||
|
||||
instr::Brk NCE::ReadBrk(u64 address, pid_t pid) const {
|
||||
long status = ptrace(PTRACE_PEEKDATA, pid ? pid : currPid, address, NULL);
|
||||
if (status == -1)
|
||||
throw exception(fmt::format("Cannot read instruction from memory, Address: {}, PID: {}, Error: {}", address, pid, strerror(errno)));
|
||||
return *(reinterpret_cast<instr::Brk *>(&status));
|
||||
}
|
||||
|
||||
void NCE::Initialize(const DeviceState &state) {
|
||||
this->state = &state;
|
||||
}
|
||||
|
||||
void NCE::Execute() {
|
||||
int status = 0;
|
||||
while (!Halt && !state->os->threadMap.empty()) {
|
||||
for (const auto &process : state->os->threadMap) {
|
||||
state->os->thisProcess = process.second;
|
||||
state->os->thisThread = process.second->threadMap.at(process.first);
|
||||
currPid = process.first;
|
||||
auto &currRegs = registerMap[currPid];
|
||||
if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Running) {
|
||||
if (waitpid(state->thisThread->pid, &status, WNOHANG) == state->thisThread->pid) {
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
|
||||
ReadRegisters(currRegs);
|
||||
auto instr = ReadBrk(currRegs.pc);
|
||||
if (instr.Verify()) {
|
||||
// We store the instruction value as the immediate value in BRK. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0.
|
||||
if (instr.value <= constant::SvcLast) {
|
||||
state->os->SvcHandler(static_cast<u16>(instr.value), currPid);
|
||||
if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Waiting)
|
||||
continue;
|
||||
} else if (instr.value > constant::SvcLast && instr.value <= constant::SvcLast + constant::NumRegs) {
|
||||
// Catch MRS that reads the value of TPIDRRO_EL0 (TLS)
|
||||
SetRegister(static_cast<Xreg>(instr.value - (constant::SvcLast + 1)), state->thisThread->tls);
|
||||
} else if (instr.value == constant::BrkRdy)
|
||||
continue;
|
||||
else
|
||||
throw exception(fmt::format("Received unhandled BRK: 0x{:X}", static_cast<u64>(instr.value)));
|
||||
}
|
||||
currRegs.pc += sizeof(u32);
|
||||
WriteRegisters(currRegs);
|
||||
ResumeProcess();
|
||||
} else {
|
||||
state->logger->Write(Logger::Debug, "Thread threw unknown signal, PID: {}, Stop Signal: {}", currPid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
|
||||
state->os->KillThread(currPid);
|
||||
}
|
||||
}
|
||||
} else if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Waiting) {
|
||||
if (state->thisThread->timeoutEnd >= std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count()) {
|
||||
state->thisThread->status = kernel::type::KThread::ThreadStatus::Running;
|
||||
SetRegister(Wreg::W0, constant::status::Timeout);
|
||||
currRegs.pc += sizeof(u32);
|
||||
WriteRegisters(currRegs);
|
||||
ResumeProcess();
|
||||
}
|
||||
} else if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Runnable) {
|
||||
state->thisThread->status = kernel::type::KThread::ThreadStatus::Running;
|
||||
currRegs.pc += sizeof(u32);
|
||||
WriteRegisters(currRegs);
|
||||
ResumeProcess();
|
||||
}
|
||||
}
|
||||
state->os->serviceManager.Loop();
|
||||
}
|
||||
}
|
||||
|
||||
void BrkLr() {
|
||||
asm("BRK #0xFF"); // BRK #constant::brkRdy
|
||||
}
|
||||
|
||||
void NCE::ExecuteFunction(void *func, user_pt_regs &funcRegs, pid_t pid) {
|
||||
pid = pid ? pid : currPid;
|
||||
bool wasRunning = PauseProcess(pid);
|
||||
user_pt_regs backupRegs{};
|
||||
ReadRegisters(backupRegs, pid);
|
||||
funcRegs.pc = reinterpret_cast<u64>(func);
|
||||
funcRegs.sp = backupRegs.sp;
|
||||
funcRegs.regs[static_cast<uint>(Xreg::X30)] = reinterpret_cast<u64>(BrkLr); // Set LR to 'brk_lr' so the application will hit a breakpoint after the function returns [LR is where the program goes after it returns from a function]
|
||||
WriteRegisters(funcRegs, pid);
|
||||
ResumeProcess(pid);
|
||||
funcRegs = WaitRdy(pid);
|
||||
WriteRegisters(backupRegs, pid);
|
||||
if (wasRunning)
|
||||
ResumeProcess(pid);
|
||||
}
|
||||
|
||||
user_pt_regs NCE::WaitRdy(pid_t pid) {
|
||||
int status;
|
||||
user_pt_regs regs{};
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) { // NOLINT(hicpp-signed-bitwise)
|
||||
ReadRegisters(regs, pid);
|
||||
auto instr = ReadBrk(regs.pc, pid);
|
||||
if (instr.Verify() && instr.value == constant::BrkRdy) {
|
||||
regs.pc += 4; // Increment program counter by a single instruction (32 bits)
|
||||
WriteRegisters(regs, pid);
|
||||
return regs;
|
||||
} else
|
||||
throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<u64>(instr.value)));
|
||||
} else
|
||||
throw exception(fmt::format("An unknown signal was caused during WaitRdy, PID: {}, Status: 0x{:X}, Signal: {}", pid, status, strsignal(WSTOPSIG(status)))); // NOLINT(hicpp-signed-bitwise)
|
||||
}
|
||||
|
||||
bool NCE::PauseProcess(pid_t pid) const {
|
||||
pid = pid ? pid : currPid;
|
||||
int status = 0;
|
||||
waitpid(pid, &status, WNOHANG);
|
||||
bool wasStopped = WIFSTOPPED(status); // NOLINT(hicpp-signed-bitwise)
|
||||
if (wasStopped) {
|
||||
if ((kill(pid, SIGSTOP) != -1) && (waitpid(pid, nullptr, 0) != -1))
|
||||
return true;
|
||||
else
|
||||
throw exception(fmt::format("Cannot pause process: {}, Error: {}", pid, strerror(errno)));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
void NCE::ResumeProcess(pid_t pid) const {
|
||||
long status = ptrace(PTRACE_CONT, pid ? pid : currPid, NULL, NULL);
|
||||
if (status == -1)
|
||||
throw exception(fmt::format("Cannot resume process: {}, Error: {}", pid, strerror(errno)));
|
||||
}
|
||||
|
||||
void NCE::StartProcess(u64 entryPoint, u64 entryArg, u64 stackTop, u32 handle, pid_t pid) const {
|
||||
user_pt_regs regs{0};
|
||||
regs.sp = stackTop;
|
||||
regs.pc = entryPoint;
|
||||
regs.regs[0] = entryArg;
|
||||
regs.regs[1] = handle;
|
||||
WriteRegisters(regs, pid);
|
||||
ResumeProcess(pid);
|
||||
}
|
||||
|
||||
u64 NCE::GetRegister(Xreg regId, pid_t pid) {
|
||||
return registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)];
|
||||
}
|
||||
|
||||
void NCE::SetRegister(Xreg regId, u64 value, pid_t pid) {
|
||||
registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)] = value;
|
||||
}
|
||||
|
||||
u64 NCE::GetRegister(Wreg regId, pid_t pid) {
|
||||
return (reinterpret_cast<u32 *>(®isterMap.at(pid ? pid : currPid).regs))[static_cast<uint>(regId) * 2];
|
||||
}
|
||||
|
||||
void NCE::SetRegister(Wreg regId, u32 value, pid_t pid) {
|
||||
(reinterpret_cast<u32 *>(®isterMap.at(pid ? pid : currPid).regs))[static_cast<uint>(regId) * 2] = value;
|
||||
}
|
||||
|
||||
u64 NCE::GetRegister(Sreg regId, pid_t pid) {
|
||||
pid = pid ? pid : currPid;
|
||||
switch (regId) {
|
||||
case Sreg::Pc:
|
||||
return registerMap.at(pid).pc;
|
||||
case Sreg::Sp:
|
||||
return registerMap.at(pid).sp;
|
||||
case Sreg::PState:
|
||||
return registerMap.at(pid).pstate;
|
||||
}
|
||||
}
|
||||
|
||||
void NCE::SetRegister(Sreg regId, u32 value, pid_t pid) {
|
||||
pid = pid ? pid : currPid;
|
||||
switch (regId) {
|
||||
case Sreg::Pc:
|
||||
registerMap.at(pid).pc = value;
|
||||
case Sreg::Sp:
|
||||
registerMap.at(pid).sp = value;
|
||||
case Sreg::PState:
|
||||
registerMap.at(pid).pstate = value;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<kernel::type::KSharedMemory> NCE::MapSharedRegion(const u64 address, const size_t size, const memory::Permission localPermission, const memory::Permission remotePermission, const memory::Type type, const memory::Region region) {
|
||||
auto item = std::make_shared<kernel::type::KSharedMemory>(0, 0, *state, size, localPermission, remotePermission, type);
|
||||
item->Map(address);
|
||||
memoryRegionMap[region] = item;
|
||||
return item;
|
||||
}
|
||||
|
||||
size_t NCE::GetSharedSize() {
|
||||
size_t sharedSize = 0;
|
||||
for (auto ®ion : memoryRegionMap)
|
||||
sharedSize += region.second->size;
|
||||
return sharedSize;
|
||||
}
|
||||
}
|
161
app/src/main/cpp/skyline/nce.h
Normal file
@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include <syslog.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "common.h"
|
||||
#include "kernel/types/KSharedMemory.h"
|
||||
|
||||
namespace skyline {
|
||||
/**
|
||||
* @brief The NCE (Native Code Execution) class is responsible for managing the state of catching instructions and directly controlling processes/threads
|
||||
*/
|
||||
class NCE {
|
||||
private:
|
||||
pid_t currPid = 0; //!< The PID of the process currently being handled, this is so the PID won't have to be passed into functions like ReadRegister redundantly
|
||||
std::unordered_map<pid_t, user_pt_regs> registerMap; //!< A map of all PIDs and their corresponding registers (Whenever they were last updated)
|
||||
const DeviceState *state; //!< The state of the device
|
||||
|
||||
/**
|
||||
* @brief Reads process registers into the `registers` variable
|
||||
* @param registers A set of registers to fill with values from the process
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void ReadRegisters(user_pt_regs ®isters, pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @brief Writes process registers from the `registers` variable
|
||||
* @param registers The registers to be written by the process
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void WriteRegisters(user_pt_regs ®isters, pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @brief Reads a BRK instruction, this is used to get it's immediate value
|
||||
* @param address The address of the BRK instruction
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return An instance of BRK with the corresponding values
|
||||
*/
|
||||
instr::Brk ReadBrk(u64 address, pid_t pid = 0) const;
|
||||
|
||||
public:
|
||||
std::map<memory::Region, std::shared_ptr<kernel::type::KSharedMemory>> memoryRegionMap; //!< A mapping from every Memory::Region to a shared pointer to it's corresponding kernel::type::KSharedMemory
|
||||
|
||||
/**
|
||||
* @brief Initialize NCE by setting the device_state variable
|
||||
* @param state The state of the device
|
||||
*/
|
||||
void Initialize(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Start the event loop of executing the program
|
||||
*/
|
||||
void Execute();
|
||||
|
||||
/**
|
||||
* @brief Execute any arbitrary function on a particular child process
|
||||
* @param func The entry point of the function
|
||||
* @param funcRegs A set of registers to run the function with (PC, SP and X29 are replaced)
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void ExecuteFunction(void *func, user_pt_regs &funcRegs, pid_t pid);
|
||||
|
||||
/**
|
||||
* @brief Waits till a process calls "BRK #constant::brk_rdy"
|
||||
* @param pid The PID of the process
|
||||
* @return The registers after the BRK
|
||||
*/
|
||||
user_pt_regs WaitRdy(pid_t pid);
|
||||
|
||||
/**
|
||||
* @brief Pauses a particular process if was not already paused
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return If the application was paused beforehand
|
||||
*/
|
||||
bool PauseProcess(pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @brief Resumes a particular process, does nothing if it was already running
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void ResumeProcess(pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @brief Starts a particular process, sets the registers to their expected values and jumps to address
|
||||
* @param entryPoint The address to jump to
|
||||
* @param entryArg The argument to pass in for the entry function
|
||||
* @param stackTop The top of the stack
|
||||
* @param handle The handle of the main thread (Set to value of 1st register)
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void StartProcess(u64 entryPoint, u64 entryArg, u64 stackTop, u32 handle, pid_t pid) const;
|
||||
|
||||
/**
|
||||
* @brief Get the value of a Xn register
|
||||
* @param regId The ID of the register
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return The value of the register
|
||||
*/
|
||||
u64 GetRegister(Xreg regId, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Set the value of a Xn register
|
||||
* @param regId The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void SetRegister(Xreg regId, u64 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Get the value of a Wn register
|
||||
* @param regId The ID of the register
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return The value in the register
|
||||
*/
|
||||
u64 GetRegister(Wreg regId, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Set the value of a Wn register
|
||||
* @param regId The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void SetRegister(Wreg regId, u32 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Get the value of a special register
|
||||
* @param regId The ID of the register
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return The value in the register
|
||||
*/
|
||||
u64 GetRegister(Sreg regId, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Set the value of a special register
|
||||
* @param regId The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void SetRegister(Sreg regId, u32 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Map a chunk of shared memory (Use only when kernel should be owner process else create KSharedMemory directly)
|
||||
* @param address The address to map to (Can be 0 if address doesn't matter)
|
||||
* @param size The size of the chunk of memory
|
||||
* @param localPermission The permissions of the memory for the kernel
|
||||
* @param remotePermission The permissions of the memory for the processes
|
||||
* @param type The type of the memory
|
||||
* @param region The specific region this memory is mapped for
|
||||
* @return A shared pointer to the kernel::type::KSharedMemory object
|
||||
*/
|
||||
std::shared_ptr<kernel::type::KSharedMemory> MapSharedRegion(const u64 address, const size_t size, const memory::Permission localPermission, const memory::Permission remotePermission, const memory::Type type, const memory::Region region);
|
||||
|
||||
/**
|
||||
* @brief Returns the total memory occupied by shared regions mapped using the kernel
|
||||
* @return The total size of allocated shared memory by NCE::MapSharedRegion
|
||||
*/
|
||||
size_t GetSharedSize();
|
||||
};
|
||||
}
|
77
app/src/main/cpp/skyline/os.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "os.h"
|
||||
#include "kernel/svc.h"
|
||||
#include "loader/nro.h"
|
||||
#include "nce.h"
|
||||
|
||||
extern bool Halt;
|
||||
|
||||
namespace skyline::kernel {
|
||||
OS::OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, thisProcess, thisThread, std::make_shared<NCE>(), settings, logger), serviceManager(state) {}
|
||||
|
||||
void OS::Execute(const std::string &romFile) {
|
||||
state.nce->Initialize(state);
|
||||
std::string romExt = romFile.substr(romFile.find_last_of('.') + 1);
|
||||
std::transform(romExt.begin(), romExt.end(), romExt.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
if (romExt == "nro")
|
||||
loader::NroLoader loader(romFile, state);
|
||||
else
|
||||
throw exception("Unsupported ROM extension.");
|
||||
|
||||
auto mainProcess = CreateProcess(state.nce->memoryRegionMap.at(memory::Region::Text)->address, constant::DefStackSize);
|
||||
mainProcess->threadMap.at(mainProcess->mainThread)->Start(); // The kernel itself is responsible for starting the main thread
|
||||
state.nce->Execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function executed by all child processes after cloning
|
||||
*/
|
||||
int ExecuteChild(void *) {
|
||||
ptrace(PTRACE_TRACEME);
|
||||
asm volatile("Brk #0xFF"); // BRK #constant::brkRdy (So we know when the thread/process is ready)
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<type::KProcess> OS::CreateProcess(u64 address, size_t stackSize) {
|
||||
auto *stack = static_cast<u8 *>(mmap(nullptr, stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
if (stack == MAP_FAILED)
|
||||
throw exception("Failed to allocate stack memory");
|
||||
if (mprotect(stack, PAGE_SIZE, PROT_NONE)) {
|
||||
munmap(stack, stackSize);
|
||||
throw exception("Failed to create guard pages");
|
||||
}
|
||||
pid_t pid = clone(&ExecuteChild, stack + stackSize, CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
|
||||
if (pid == -1)
|
||||
throw exception(fmt::format("Call to clone() has failed: {}", strerror(errno)));
|
||||
std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(0, pid, state, address, reinterpret_cast<u64>(stack), stackSize);
|
||||
threadMap[pid] = process;
|
||||
processVec.push_back(pid);
|
||||
state.logger->Write(Logger::Debug, "Successfully created process with PID: {}", pid);
|
||||
return process;
|
||||
}
|
||||
|
||||
void OS::KillThread(pid_t pid) {
|
||||
auto process = threadMap.at(pid);
|
||||
if (process->mainThread == pid) {
|
||||
state.logger->Write(Logger::Debug, "Exiting process with PID: {}", pid);
|
||||
// Erasing all shared_ptr instances to the process will call the destructor
|
||||
// However, in the case these are not all instances of it we wouldn't want to call the destructor
|
||||
for (auto&[key, value]: process->threadMap)
|
||||
threadMap.erase(key);
|
||||
processVec.erase(std::remove(processVec.begin(), processVec.end(), pid), processVec.end());
|
||||
} else {
|
||||
state.logger->Write(Logger::Debug, "Exiting thread with TID: {}", pid);
|
||||
process->handleTable.erase(process->threadMap[pid]->handle);
|
||||
process->threadMap.erase(pid);
|
||||
threadMap.erase(pid);
|
||||
}
|
||||
}
|
||||
|
||||
void OS::SvcHandler(u16 svc, pid_t pid) {
|
||||
if (svc::SvcTable[svc]) {
|
||||
state.logger->Write(Logger::Debug, "SVC called 0x{:X}", svc);
|
||||
(*svc::SvcTable[svc])(state);
|
||||
} else
|
||||
throw exception(fmt::format("Unimplemented SVC 0x{:X}", svc));
|
||||
}
|
||||
}
|
60
app/src/main/cpp/skyline/os.h
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <thread>
|
||||
#include "common.h"
|
||||
#include "kernel/ipc.h"
|
||||
#include "kernel/types/KProcess.h"
|
||||
#include "kernel/types/KThread.h"
|
||||
#include "kernel/services/serviceman.h"
|
||||
#include "nce.h"
|
||||
|
||||
namespace skyline::kernel {
|
||||
/**
|
||||
* @brief The OS class manages the interaction between Skyline components and the underlying OS in NCE
|
||||
*/
|
||||
class OS {
|
||||
private:
|
||||
DeviceState state; //!< The state of the device
|
||||
|
||||
public:
|
||||
std::unordered_map<pid_t, std::shared_ptr<type::KProcess>> threadMap; //!< A mapping from a threat's PID to it's KProcess object
|
||||
std::vector<pid_t> processVec; //!< A vector of all processes by their main thread's PID
|
||||
std::shared_ptr<type::KProcess> thisProcess; //!< The corresponding KProcess object of the process that's called an SVC
|
||||
std::shared_ptr<type::KThread> thisThread; //!< The corresponding KThread object of the thread that's called an SVC
|
||||
service::ServiceManager serviceManager; //!< This manages all of the service functions
|
||||
|
||||
/**
|
||||
* @param logger An instance of the Logger class
|
||||
* @param settings An instance of the Settings class
|
||||
*/
|
||||
OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings);
|
||||
|
||||
/**
|
||||
* @brief Execute a particular ROM file. This launches a the main processes and calls the NCE class to handle execution.
|
||||
* @param romFile The path to the ROM file to execute
|
||||
*/
|
||||
void Execute(const std::string &romFile);
|
||||
|
||||
/**
|
||||
* @brief Creates a new process
|
||||
* @param address The address of the initial function
|
||||
* @param stackSize The size of the main stack
|
||||
* @return An instance of the KProcess of the created process
|
||||
*/
|
||||
std::shared_ptr<type::KProcess> CreateProcess(u64 address, size_t stackSize);
|
||||
|
||||
/**
|
||||
* @brief Kill a particular thread
|
||||
* @param pid The PID of the thread
|
||||
*/
|
||||
void KillThread(pid_t pid);
|
||||
|
||||
/**
|
||||
* @brief Handles a particular SuperVisor Call
|
||||
* @param svc The ID of the SVC to be called
|
||||
* @param pid The PID of the process/thread calling the SVC
|
||||
*/
|
||||
void SvcHandler(u16 svc, pid_t pid);
|
||||
};
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
#include "common.h"
|
||||
#include <tinyxml2.h>
|
||||
#include <syslog.h>
|
||||
|
||||
namespace lightSwitch {
|
||||
Settings::Settings(std::string &pref_xml) {
|
||||
tinyxml2::XMLDocument pref;
|
||||
if (pref.LoadFile(pref_xml.c_str()))
|
||||
throw exception("TinyXML2 Error: " + std::string(pref.ErrorStr()));
|
||||
tinyxml2::XMLElement *elem = pref.LastChild()->FirstChild()->ToElement();
|
||||
while (elem) {
|
||||
switch (elem->Value()[0]) {
|
||||
case 's':
|
||||
string_map.insert(
|
||||
std::pair<char *, char *>((char *) elem->FindAttribute("name")->Value(), (char *) elem->GetText()));
|
||||
break;
|
||||
case 'b':
|
||||
bool_map.insert(std::pair<char *, bool>((char *) elem->FindAttribute("name")->Value(), elem->FindAttribute("value")->BoolValue()));
|
||||
default:
|
||||
break;
|
||||
};
|
||||
if (elem->NextSibling())
|
||||
elem = elem->NextSibling()->ToElement();
|
||||
else break;
|
||||
}
|
||||
pref.Clear();
|
||||
}
|
||||
|
||||
char *Settings::GetString(char *key) {
|
||||
return string_map.at(key);
|
||||
}
|
||||
|
||||
bool Settings::GetBool(char *key) {
|
||||
return bool_map.at(key);
|
||||
}
|
||||
|
||||
void Settings::List() {
|
||||
auto it_s = string_map.begin();
|
||||
while (it_s != string_map.end()) {
|
||||
syslog(LOG_INFO, "Key: %s", it_s->first);
|
||||
syslog(LOG_INFO, "Value: %s", GetString(it_s->first));
|
||||
it_s++;
|
||||
}
|
||||
auto it_b = bool_map.begin();
|
||||
while (it_b != bool_map.end()) {
|
||||
syslog(LOG_INFO, "Key: %s", it_b->first);
|
||||
syslog(LOG_INFO, "Value: %i", GetBool(it_b->first));
|
||||
it_b++;
|
||||
}
|
||||
}
|
||||
|
||||
Logger::Logger(const std::string &log_path) {
|
||||
log_file.open(log_path, std::ios::app);
|
||||
WriteHeader("Logging started");
|
||||
}
|
||||
|
||||
Logger::~Logger() {
|
||||
WriteHeader("Logging ended");
|
||||
}
|
||||
|
||||
void Logger::WriteHeader(const std::string &str) {
|
||||
syslog(LOG_ALERT, "%s", str.c_str());
|
||||
log_file << "0|" << str << "\n";
|
||||
log_file.flush();
|
||||
}
|
||||
|
||||
void Logger::Write(const LogLevel level, const std::string &str) {
|
||||
#ifdef NDEBUG
|
||||
if (level == DEBUG) return;
|
||||
#endif
|
||||
syslog(level_syslog[level], "%s", str.c_str());
|
||||
log_file << "1|" << level_str[level] << "|" << str << "\n";
|
||||
log_file.flush();
|
||||
}
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <syslog.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <fmt/format.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace lightSwitch {
|
||||
// Global typedefs
|
||||
typedef __uint128_t u128;
|
||||
typedef __uint64_t u64;
|
||||
typedef __uint32_t u32;
|
||||
typedef __uint16_t u16;
|
||||
typedef __uint8_t u8;
|
||||
typedef __int128_t i128;
|
||||
typedef __int64_t i64;
|
||||
typedef __int32_t i32;
|
||||
typedef __int16_t i16;
|
||||
typedef __int8_t i8;
|
||||
typedef std::runtime_error exception; //!< This is used as the default exception
|
||||
typedef u32 handle_t; //!< The type of an handle
|
||||
|
||||
namespace constant {
|
||||
// Memory
|
||||
constexpr u64 base_addr = 0x8000000; //!< The address space base
|
||||
constexpr u64 map_addr = base_addr + 0x80000000; //!< The address of the map region
|
||||
constexpr u64 base_size = 0x7FF8000000; //!< The size of the address space
|
||||
constexpr u64 map_size = 0x1000000000; //!< The size of the map region
|
||||
constexpr u64 total_phy_mem = 0xF8000000; // ~4 GB of RAM
|
||||
constexpr size_t def_stack_size = 0x1E8480; //!< The default amount of stack: 2 MB
|
||||
constexpr size_t def_heap_size = PAGE_SIZE; //!< The default amount of heap
|
||||
constexpr size_t tls_slot_size = 0x200; //!< The size of a single TLS slot
|
||||
constexpr u8 tls_slots = PAGE_SIZE / tls_slot_size; //!< The amount of TLS slots in a single page
|
||||
// Loader
|
||||
constexpr u32 nro_magic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file
|
||||
// NCE
|
||||
constexpr u8 num_regs = 31; //!< The amount of registers that ARMv8 has
|
||||
constexpr u16 svc_last = 0x7F; //!< The index of the last SVC
|
||||
constexpr u16 brk_rdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
|
||||
constexpr u32 tpidrro_el0 = 0x5E83; //!< ID of tpidrro_el0 in MRS
|
||||
// IPC
|
||||
constexpr size_t tls_ipc_size = 0x100; //!< The size of the IPC command buffer in a TLS slot
|
||||
constexpr handle_t sm_handle = 0xD000; //!< sm:'s handle
|
||||
constexpr u8 port_size = 0x8; //!< The size of a port name string
|
||||
constexpr u32 sfco_magic = 0x4F434653; //!< SFCO in reverse, written to IPC messages
|
||||
constexpr u32 sfci_magic = 0x49434653; //!< SFCI in reverse, present in received IPC messages
|
||||
constexpr u64 padding_sum = 0x10; //!< The sum of the padding surrounding DataPayload
|
||||
// Process
|
||||
constexpr handle_t base_handle_index = sm_handle + 1; // The index of the base handle
|
||||
constexpr u8 default_priority = 31; //!< The default priority of a process
|
||||
constexpr std::pair<int8_t, int8_t> priority_an = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
|
||||
constexpr std::pair<u8, u8> priority_nin = {0, 63}; //!< The range of priority for the Nintendo Switch
|
||||
// Status codes
|
||||
namespace status {
|
||||
constexpr u32 success = 0x0; //!< "Success"
|
||||
constexpr u32 inv_address = 0xCC01; //!< "Invalid address"
|
||||
constexpr u32 inv_handle = 0xE401; //!< "Invalid handle"
|
||||
constexpr u32 unimpl = 0x177202; //!< "Unimplemented behaviour"
|
||||
}
|
||||
};
|
||||
|
||||
namespace instr {
|
||||
/**
|
||||
* A bit-field struct that encapsulates a BRK instruction. It can be used to generate as well as parse the instruction's opcode. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction.
|
||||
*/
|
||||
struct brk {
|
||||
/**
|
||||
* Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
|
||||
* @param val The immediate value of the instruction
|
||||
*/
|
||||
brk(u16 val) {
|
||||
start = 0x0; // First 5 bits of an BRK instruction are 0
|
||||
value = val;
|
||||
end = 0x6A1; // Last 11 bits of an BRK instruction stored as u16
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the opcode represents a valid BRK instruction
|
||||
*/
|
||||
bool verify() {
|
||||
return (start == 0x0 && end == 0x6A1);
|
||||
}
|
||||
|
||||
u8 start : 5;
|
||||
u32 value : 16;
|
||||
u16 end : 11;
|
||||
};
|
||||
|
||||
static_assert(sizeof(brk) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* A bit-field struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call.
|
||||
*/
|
||||
struct svc {
|
||||
/**
|
||||
* @return If the opcode represents a valid SVC instruction
|
||||
*/
|
||||
bool verify() {
|
||||
return (start == 0x1 && end == 0x6A0);
|
||||
}
|
||||
|
||||
u8 start : 5;
|
||||
u32 value : 16;
|
||||
u16 end : 11;
|
||||
};
|
||||
|
||||
static_assert(sizeof(svc) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* A bit-field struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register.
|
||||
*/
|
||||
struct mrs {
|
||||
/**
|
||||
* @return If the opcode represents a valid MRS instruction
|
||||
*/
|
||||
bool verify() {
|
||||
return (end == 0xD53);
|
||||
}
|
||||
|
||||
u8 dst_reg : 5;
|
||||
u32 src_reg : 15;
|
||||
u16 end : 12;
|
||||
};
|
||||
|
||||
static_assert(sizeof(mrs) == sizeof(u32));
|
||||
};
|
||||
|
||||
/**
|
||||
* Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers
|
||||
*/
|
||||
enum class xreg { x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30 };
|
||||
enum class wreg { w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30 };
|
||||
enum class sreg { sp, pc, pstate };
|
||||
|
||||
/**
|
||||
* The Settings class is used to access the parameters set in the Java component of the application
|
||||
*/
|
||||
class Settings {
|
||||
private:
|
||||
struct KeyCompare {
|
||||
bool operator()(char const *a, char const *b) const {
|
||||
return std::strcmp(a, b) < 0;
|
||||
}
|
||||
}; //!< This is a comparision operator between strings, implemented to store strings in a std::map
|
||||
|
||||
std::map<char *, char *, KeyCompare> string_map; //!< A mapping from all keys to their corresponding string value
|
||||
std::map<char *, bool, KeyCompare> bool_map; //!< A mapping from all keys to their corresponding boolean value
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param pref_xml The path to the preference XML file
|
||||
*/
|
||||
Settings(std::string &pref_xml);
|
||||
|
||||
/**
|
||||
* @param key The key of the setting
|
||||
* @return The string value of the setting
|
||||
*/
|
||||
char *GetString(char *key);
|
||||
|
||||
/**
|
||||
* @param key The key of the setting
|
||||
* @return The boolean value of the setting
|
||||
*/
|
||||
bool GetBool(char *key);
|
||||
|
||||
/**
|
||||
* Writes all settings keys and values to syslog. This function is for development purposes.
|
||||
*/
|
||||
void List();
|
||||
};
|
||||
|
||||
/**
|
||||
* The Logger class is to generate a log of the program
|
||||
*/
|
||||
class Logger {
|
||||
private:
|
||||
std::ofstream log_file; //!< An output stream to the log file
|
||||
const char *level_str[4] = {"0", "1", "2", "3"}; //!< This is used to denote the LogLevel when written out to a file
|
||||
static constexpr int level_syslog[4] = {LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG}; //!< This corresponds to LogLevel and provides it's equivalent for syslog
|
||||
|
||||
public:
|
||||
enum LogLevel { ERROR, WARN, INFO, DEBUG }; //!< The level of a particular log
|
||||
|
||||
/**
|
||||
* @param log_path The path to the log file
|
||||
*/
|
||||
Logger(const std::string &log_path);
|
||||
|
||||
/**
|
||||
* Writes "Logging ended" to as a header
|
||||
*/
|
||||
~Logger();
|
||||
|
||||
/**
|
||||
* Writes a header, should only be used for emulation starting and ending
|
||||
* @param str The value to be written
|
||||
*/
|
||||
void WriteHeader(const std::string &str);
|
||||
|
||||
/**
|
||||
* Write a log to the log file
|
||||
* @param level The level of the log
|
||||
* @param str The value to be written
|
||||
*/
|
||||
void Write(const LogLevel level, const std::string &str);
|
||||
|
||||
/**
|
||||
* Write a log to the log file with libfmt formatting
|
||||
* @param level The level of the log
|
||||
* @param format_str The value to be written, with libfmt formatting
|
||||
* @param args The arguments based on format_str
|
||||
*/
|
||||
template<typename S, typename... Args>
|
||||
void Write(Logger::LogLevel level, const S &format_str, Args &&... args) {
|
||||
#ifdef NDEBUG
|
||||
if (level == DEBUG) return;
|
||||
#endif
|
||||
Write(level, fmt::format(format_str, args...));
|
||||
}
|
||||
};
|
||||
|
||||
// Predeclare some classes here as we use them in device_state
|
||||
class NCE;
|
||||
namespace kernel {
|
||||
namespace type {
|
||||
class KProcess;
|
||||
|
||||
class KThread;
|
||||
}
|
||||
class OS;
|
||||
}
|
||||
|
||||
/**
|
||||
* This struct is used to hold the state of a device
|
||||
*/
|
||||
struct device_state {
|
||||
device_state(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &this_process, std::shared_ptr<kernel::type::KThread> &this_thread, std::shared_ptr<NCE> nce, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger) : os(os), nce(nce), settings(settings), logger(logger), this_process(this_process), this_thread(this_thread) {}
|
||||
|
||||
kernel::OS *os; // Because OS holds the device_state struct, it's destruction will accompany that of device_state
|
||||
std::shared_ptr<kernel::type::KProcess> &this_process;
|
||||
std::shared_ptr<kernel::type::KThread> &this_thread;
|
||||
std::shared_ptr<NCE> nce;
|
||||
std::shared_ptr<Settings> settings;
|
||||
std::shared_ptr<Logger> logger;
|
||||
};
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
#include <syslog.h>
|
||||
#include <cstdlib>
|
||||
#include "ipc.h"
|
||||
#include "types/KProcess.h"
|
||||
|
||||
namespace lightSwitch::kernel::ipc {
|
||||
IpcRequest::IpcRequest(bool is_domain, device_state &state) : is_domain(is_domain), state(state), tls() {
|
||||
u8 *curr_ptr = tls.data();
|
||||
state.this_process->ReadMemory(curr_ptr, state.this_thread->tls, constant::tls_ipc_size);
|
||||
header = reinterpret_cast<CommandHeader *>(curr_ptr);
|
||||
curr_ptr += sizeof(CommandHeader);
|
||||
if (header->handle_desc) {
|
||||
handle_desc = reinterpret_cast<HandleDescriptor *>(curr_ptr);
|
||||
curr_ptr += sizeof(HandleDescriptor) + (handle_desc->send_pid ? sizeof(u8) : 0);
|
||||
for (uint index = 0; handle_desc->copy_count > index; index++) {
|
||||
copy_handles.push_back(*reinterpret_cast<handle_t *>(curr_ptr));
|
||||
curr_ptr += sizeof(handle_t);
|
||||
}
|
||||
for (uint index = 0; handle_desc->move_count > index; index++) {
|
||||
move_handles.push_back(*reinterpret_cast<handle_t *>(curr_ptr));
|
||||
curr_ptr += sizeof(handle_t);
|
||||
}
|
||||
}
|
||||
for (uint index = 0; header->x_no > index; index++) {
|
||||
vec_buf_x.push_back(reinterpret_cast<BufferDescriptorX *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorX);
|
||||
}
|
||||
for (uint index = 0; header->a_no > index; index++) {
|
||||
vec_buf_a.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
for (uint index = 0; header->b_no > index; index++) {
|
||||
vec_buf_b.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
for (uint index = 0; header->w_no > index; index++) {
|
||||
vec_buf_w.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
auto raw_ptr = reinterpret_cast<u8 *>((((reinterpret_cast<u64>(curr_ptr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::padding_sum - 1U)) + constant::padding_sum + reinterpret_cast<u64>(tls.data())); // Align to 16 bytes relative to start of TLS
|
||||
if (is_domain) {
|
||||
domain = reinterpret_cast<DomainHeaderRequest *>(raw_ptr);
|
||||
payload = reinterpret_cast<PayloadHeader *>(raw_ptr + sizeof(DomainHeaderRequest));
|
||||
cmd_arg_sz = domain->payload_sz - sizeof(PayloadHeader);
|
||||
} else {
|
||||
payload = reinterpret_cast<PayloadHeader *>(raw_ptr);
|
||||
cmd_arg_sz = (header->raw_sz * sizeof(u32)) - (constant::padding_sum + sizeof(PayloadHeader));
|
||||
}
|
||||
if (payload->magic != constant::sfci_magic) throw exception(fmt::format("Unexpected Magic in PayloadHeader: 0x{:X}", reinterpret_cast<u32>(payload->magic)));
|
||||
cmd_arg = reinterpret_cast<u8 *>(payload) + sizeof(PayloadHeader);
|
||||
curr_ptr += header->raw_sz * sizeof(u32);
|
||||
if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) {
|
||||
vec_buf_c.push_back(reinterpret_cast<BufferDescriptorC *>(curr_ptr));
|
||||
} else if (header->c_flag > static_cast<u8>(BufferCFlag::SingleDescriptor)) {
|
||||
for (uint index = 0; (header->c_flag - 2) > index; index++) { // (c_flag - 2) C descriptors are present
|
||||
vec_buf_c.push_back(reinterpret_cast<BufferDescriptorC *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcResponse::IpcResponse(bool is_domain, device_state &state) : is_domain(is_domain), state(state) {}
|
||||
|
||||
void IpcResponse::WriteTls() {
|
||||
std::array<u8, constant::tls_ipc_size> tls{};
|
||||
u8 *curr_ptr = tls.data();
|
||||
auto header = reinterpret_cast<CommandHeader *>(curr_ptr);
|
||||
header->x_no = static_cast<u8>(vec_buf_x.size());
|
||||
header->a_no = static_cast<u8>(vec_buf_a.size());
|
||||
header->b_no = static_cast<u8>(vec_buf_b.size());
|
||||
header->w_no = static_cast<u8>(vec_buf_w.size());
|
||||
header->raw_sz = static_cast<u32>((sizeof(PayloadHeader) + arg_vec.size() + constant::padding_sum + (is_domain ? sizeof(DomainHeaderRequest) : 0)) / sizeof(u32)); // Size is in 32-bit units because Nintendo
|
||||
if (!vec_buf_c.empty())
|
||||
header->c_flag = (vec_buf_c.size() == 1) ? static_cast<u8>(BufferCFlag::SingleDescriptor) : static_cast<u8>(vec_buf_c.size() + static_cast<u8>(BufferCFlag::SingleDescriptor));
|
||||
header->handle_desc = (!copy_handles.empty() || !move_handles.empty());
|
||||
curr_ptr += sizeof(CommandHeader);
|
||||
if (header->handle_desc) {
|
||||
auto handle_desc = reinterpret_cast<HandleDescriptor *>(curr_ptr);
|
||||
handle_desc->send_pid = false; // TODO: Figure this out ?
|
||||
handle_desc->copy_count = static_cast<u8>(copy_handles.size());
|
||||
handle_desc->move_count = static_cast<u8>(move_handles.size());
|
||||
curr_ptr += sizeof(HandleDescriptor);
|
||||
for (uint index = 0; handle_desc->copy_count > index; index++) {
|
||||
*reinterpret_cast<handle_t *>(curr_ptr) = copy_handles[index];
|
||||
curr_ptr += sizeof(handle_t);
|
||||
}
|
||||
for (uint index = 0; handle_desc->move_count > index; index++) {
|
||||
*reinterpret_cast<handle_t *>(curr_ptr) = move_handles[index];
|
||||
curr_ptr += sizeof(handle_t);
|
||||
}
|
||||
}
|
||||
for (uint index = 0; header->x_no > index; index++) {
|
||||
*reinterpret_cast<BufferDescriptorX *>(curr_ptr) = vec_buf_x[index];
|
||||
curr_ptr += sizeof(BufferDescriptorX);
|
||||
}
|
||||
for (uint index = 0; header->a_no > index; index++) {
|
||||
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_a[index];
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
for (uint index = 0; header->b_no > index; index++) {
|
||||
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_b[index];
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
for (uint index = 0; header->w_no > index; index++) {
|
||||
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_w[index];
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
u64 padding = ((((reinterpret_cast<u64>(curr_ptr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::padding_sum - 1U)) + constant::padding_sum + (reinterpret_cast<u64>(tls.data()) - reinterpret_cast<u64>(curr_ptr))); // Calculate the amount of padding at the front
|
||||
curr_ptr += padding;
|
||||
PayloadHeader *payload;
|
||||
if (is_domain) {
|
||||
auto domain = reinterpret_cast<DomainHeaderResponse *>(curr_ptr);
|
||||
domain->output_count = 0; // TODO: Figure this out
|
||||
payload = reinterpret_cast<PayloadHeader *>(curr_ptr + sizeof(DomainHeaderResponse));
|
||||
} else {
|
||||
payload = reinterpret_cast<PayloadHeader *>(curr_ptr);
|
||||
}
|
||||
payload->magic = constant::sfco_magic;
|
||||
payload->version = 1;
|
||||
payload->value = error_code;
|
||||
curr_ptr += sizeof(PayloadHeader);
|
||||
if (!arg_vec.empty()) memcpy(curr_ptr, arg_vec.data(), arg_vec.size());
|
||||
curr_ptr += arg_vec.size() + (constant::padding_sum - padding);
|
||||
if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) {
|
||||
*reinterpret_cast<BufferDescriptorC *>(curr_ptr) = vec_buf_c[0];
|
||||
} else if (header->c_flag > static_cast<u8>(BufferCFlag::SingleDescriptor)) {
|
||||
for (uint index = 0; (header->c_flag - 2) > index; index++) {
|
||||
*reinterpret_cast<BufferDescriptorC *>(curr_ptr) = vec_buf_c[index];
|
||||
curr_ptr += sizeof(BufferDescriptorC);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include "switch/common.h"
|
||||
|
||||
namespace lightSwitch::kernel::ipc {
|
||||
/**
|
||||
* This bit-field structure holds the header of an IPC command.
|
||||
* https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
|
||||
*/
|
||||
struct CommandHeader {
|
||||
u16 type : 16;
|
||||
u8 x_no : 4;
|
||||
u8 a_no : 4;
|
||||
u8 b_no : 4;
|
||||
u8 w_no : 4;
|
||||
u32 raw_sz : 10;
|
||||
u8 c_flag : 4;
|
||||
u32 : 17;
|
||||
bool handle_desc : 1;
|
||||
};
|
||||
static_assert(sizeof(CommandHeader) == 8);
|
||||
|
||||
/**
|
||||
* This reflects the value in CommandStruct::type
|
||||
*/
|
||||
enum class CommandType : u16 {
|
||||
Invalid = 0, LegacyRequest = 1, Close = 2, LegacyControl = 3, Request = 4, Control = 5, RequestWithContext = 6, ControlWithContext = 7
|
||||
};
|
||||
|
||||
/**
|
||||
* This reflects the value in CommandStruct::c_flags
|
||||
*/
|
||||
enum class BufferCFlag : u8 {
|
||||
None = 0, InlineDescriptor = 1, SingleDescriptor = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* This bit-field structure holds the handle descriptor of a recieved IPC command.
|
||||
* https://switchbrew.org/wiki/IPC_Marshalling#Handle_descriptor
|
||||
*/
|
||||
struct HandleDescriptor {
|
||||
bool send_pid : 1;
|
||||
u32 copy_count : 4;
|
||||
u32 move_count : 4;
|
||||
u32 : 23;
|
||||
};
|
||||
static_assert(sizeof(HandleDescriptor) == 4);
|
||||
|
||||
/**
|
||||
* This bit-field structure holds the domain's header of an IPC request command.
|
||||
* https://switchbrew.org/wiki/IPC_Marshalling#Domains
|
||||
*/
|
||||
struct DomainHeaderRequest {
|
||||
u8 command : 8;
|
||||
u8 input_count : 8;
|
||||
u16 payload_sz : 16;
|
||||
u32 object_id : 32;
|
||||
u32 : 32;
|
||||
u32 token : 32;
|
||||
};
|
||||
static_assert(sizeof(DomainHeaderRequest) == 16);
|
||||
|
||||
/**
|
||||
* This bit-field structure holds the domain's header of an IPC response command.
|
||||
* https://switchbrew.org/wiki/IPC_Marshalling#Domains
|
||||
*/
|
||||
struct DomainHeaderResponse {
|
||||
u64 output_count : 32;
|
||||
u64 : 32;
|
||||
u64 : 64;
|
||||
};
|
||||
static_assert(sizeof(DomainHeaderResponse) == 16);
|
||||
|
||||
/**
|
||||
* This bit-field structure holds the data payload of an IPC command.
|
||||
* https://switchbrew.org/wiki/IPC_Marshalling#Data_payload
|
||||
*/
|
||||
struct PayloadHeader {
|
||||
u32 magic : 32;
|
||||
u32 version : 32;
|
||||
u32 value : 32;
|
||||
u32 token : 32;
|
||||
};
|
||||
static_assert(sizeof(PayloadHeader) == 16);
|
||||
|
||||
/**
|
||||
* This is a buffer descriptor for X buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_X_.22Pointer.22
|
||||
*/
|
||||
struct BufferDescriptorX {
|
||||
u16 counter_0_5 : 6;
|
||||
u16 address_36_38 : 3;
|
||||
u16 counter_9_11 : 3;
|
||||
u16 address_32_35 : 4;
|
||||
u16 size : 16;
|
||||
u32 address_0_31 : 32;
|
||||
|
||||
BufferDescriptorX(u64 address, u16 counter, u16 size) : size(size) {
|
||||
// Test: The AND mask might be the other way around
|
||||
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
|
||||
address_32_35 = static_cast<u16>(address & 0x78000000);
|
||||
address_36_38 = static_cast<u16>(address & 0x7000000);
|
||||
counter_0_5 = static_cast<u16>(address & 0x7E00);
|
||||
counter_9_11 = static_cast<u16>(address & 0x38);
|
||||
}
|
||||
|
||||
inline u64 Address() const {
|
||||
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_35) << 32 | static_cast<u64>(address_36_38) << 36;
|
||||
}
|
||||
|
||||
inline u16 Counter() const {
|
||||
return static_cast<u16>(counter_0_5) | static_cast<u16>(counter_9_11) << 9;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BufferDescriptorX) == 8);
|
||||
|
||||
/**
|
||||
* This is a buffer descriptor for A/B/W buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_A.2FB.2FW_.22Send.22.2F.22Receive.22.2F.22Exchange.22
|
||||
*/
|
||||
struct BufferDescriptorABW {
|
||||
u32 size_0_31 : 32;
|
||||
u32 address_0_31 : 32;
|
||||
u8 flags : 2;
|
||||
u8 address_36_38 : 3;
|
||||
u32 : 19;
|
||||
u8 size_32_35 : 4;
|
||||
u8 address_32_35 : 4;
|
||||
|
||||
BufferDescriptorABW(u64 address, u64 size) {
|
||||
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
|
||||
address_32_35 = static_cast<u8>(address & 0x78000000);
|
||||
address_36_38 = static_cast<u8>(address & 0x7000000);
|
||||
size_0_31 = static_cast<u32>(size & 0x7FFFFFFF80000000);
|
||||
size_32_35 = static_cast<u8>(size & 0x78000000);
|
||||
}
|
||||
|
||||
inline u64 Address() const {
|
||||
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_35) << 32 | static_cast<u64>(address_36_38) << 36;
|
||||
}
|
||||
|
||||
inline u64 Size() const {
|
||||
return static_cast<u64>(size_0_31) | static_cast<u64>(size_32_35) << 32;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BufferDescriptorABW) == 12);
|
||||
|
||||
/**
|
||||
* This is a buffer descriptor for C buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_C_.22ReceiveList.22
|
||||
*/
|
||||
struct BufferDescriptorC {
|
||||
u32 address_0_31 : 32;
|
||||
u16 address_32_48 : 16;
|
||||
u16 size : 16;
|
||||
|
||||
BufferDescriptorC(u64 address, u16 size) : size(size) {
|
||||
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
|
||||
address_32_48 = static_cast<u16>(address & 0x7FFFC000);
|
||||
}
|
||||
|
||||
inline u64 Address() const {
|
||||
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_48) << 32;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BufferDescriptorC) == 8);
|
||||
|
||||
class IpcRequest {
|
||||
private:
|
||||
device_state &state;
|
||||
|
||||
public:
|
||||
std::array<u8, constant::tls_ipc_size> tls;
|
||||
CommandHeader *header{};
|
||||
HandleDescriptor *handle_desc{};
|
||||
bool is_domain{};
|
||||
DomainHeaderRequest *domain{};
|
||||
PayloadHeader *payload{};
|
||||
u8 *cmd_arg{};
|
||||
u64 cmd_arg_sz{};
|
||||
std::vector<handle_t> copy_handles;
|
||||
std::vector<handle_t> move_handles;
|
||||
std::vector<BufferDescriptorX *> vec_buf_x;
|
||||
std::vector<BufferDescriptorABW *> vec_buf_a;
|
||||
std::vector<BufferDescriptorABW *> vec_buf_b;
|
||||
std::vector<BufferDescriptorABW *> vec_buf_w;
|
||||
std::vector<BufferDescriptorC *> vec_buf_c;
|
||||
|
||||
IpcRequest(bool is_domain, device_state &state);
|
||||
};
|
||||
|
||||
class IpcResponse {
|
||||
private:
|
||||
std::vector<u8> arg_vec;
|
||||
device_state &state;
|
||||
|
||||
public:
|
||||
bool is_domain{};
|
||||
u32 error_code{};
|
||||
std::vector<handle_t> copy_handles;
|
||||
std::vector<handle_t> move_handles;
|
||||
std::vector<BufferDescriptorX> vec_buf_x;
|
||||
std::vector<BufferDescriptorABW> vec_buf_a;
|
||||
std::vector<BufferDescriptorABW> vec_buf_b;
|
||||
std::vector<BufferDescriptorABW> vec_buf_w;
|
||||
std::vector<BufferDescriptorC> vec_buf_c;
|
||||
|
||||
IpcResponse(bool is_domain, device_state &state);
|
||||
|
||||
template<typename T>
|
||||
void WriteValue(const T &value) {
|
||||
arg_vec.reserve(arg_vec.size() + sizeof(T));
|
||||
auto item = reinterpret_cast<const u8 *>(&value);
|
||||
for (uint index = 0; sizeof(T) > index; index++) {
|
||||
arg_vec.push_back(*item);
|
||||
item++;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteTls();
|
||||
};
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
#include "service.h"
|
||||
|
||||
namespace lightSwitch::kernel::service {
|
||||
|
||||
Service::Service() {
|
||||
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../nce.h"
|
||||
|
||||
namespace lightSwitch::kernel::service {
|
||||
class Service {
|
||||
Service();
|
||||
};
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
#include "../../common.h"
|
||||
#include "../ipc.h"
|
||||
|
||||
namespace lightSwitch::kernel::service {
|
||||
class BaseService {
|
||||
protected:
|
||||
device_state &state;
|
||||
|
||||
public:
|
||||
BaseService(device_state &state) : state(state) {}
|
||||
|
||||
virtual const char *Name() = 0;
|
||||
|
||||
virtual ipc::IpcResponse HandleSyncRequest(ipc::IpcRequest &request) = 0;
|
||||
|
||||
|
||||
};
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <syslog.h>
|
||||
#include <utility>
|
||||
#include "svc.h"
|
||||
|
||||
namespace lightSwitch::kernel::svc {
|
||||
void SetHeapSize(device_state &state) {
|
||||
auto heap = state.this_process->MapPrivateRegion(0, state.nce->GetRegister(wreg::w1), {true, true, false}, Memory::Type::Heap, Memory::Region::heap);
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
state.nce->SetRegister(xreg::x1, heap->address);
|
||||
state.logger->Write(Logger::DEBUG, "Heap size was set to 0x{:X}", state.nce->GetRegister(wreg::w1));
|
||||
}
|
||||
|
||||
void QueryMemory(device_state &state) {
|
||||
Memory::MemoryInfo mem_inf;
|
||||
u64 addr = state.nce->GetRegister(xreg::x2);
|
||||
if (state.nce->memory_map.count(addr)) {
|
||||
mem_inf = state.nce->memory_map.at(addr)->GetInfo(state.this_process->main_thread);
|
||||
} else if (state.this_process->memory_map.count(addr)) {
|
||||
mem_inf = state.this_process->memory_map.at(addr)->GetInfo();
|
||||
} else {
|
||||
state.nce->SetRegister(wreg::w0, constant::status::inv_address);
|
||||
return;
|
||||
}
|
||||
state.this_process->WriteMemory<Memory::MemoryInfo>(mem_inf, state.nce->GetRegister(xreg::x0));
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
}
|
||||
|
||||
void CreateThread(device_state &state) {
|
||||
// TODO: Check if the values supplied by the process are actually valid & Support Core Mask potentially ?
|
||||
auto thread = state.this_process->CreateThread(state.nce->GetRegister(xreg::x1), state.nce->GetRegister(xreg::x2), state.nce->GetRegister(xreg::x3), static_cast<u8>(state.nce->GetRegister(wreg::w4)));
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
state.nce->SetRegister(wreg::w1, thread->handle);
|
||||
}
|
||||
|
||||
void StartThread(device_state &state) {
|
||||
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
|
||||
if (object->type == type::KObjectType::KThread) {
|
||||
std::static_pointer_cast<type::KThread>(object)->Start();
|
||||
} else
|
||||
throw exception("StartThread was called on a non-KThread object");
|
||||
}
|
||||
|
||||
void ExitThread(device_state &state) {
|
||||
state.os->KillThread(state.this_thread->pid);
|
||||
}
|
||||
|
||||
void GetThreadPriority(device_state &state) {
|
||||
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
|
||||
if (object->type == type::KObjectType::KThread) {
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
state.nce->SetRegister(wreg::w1, std::static_pointer_cast<type::KThread>(object)->priority);
|
||||
} else
|
||||
throw exception("GetThreadPriority was called on a non-KThread object");
|
||||
}
|
||||
|
||||
void SetThreadPriority(device_state &state) {
|
||||
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
|
||||
if (object->type == type::KObjectType::KThread) {
|
||||
std::static_pointer_cast<type::KThread>(object)->Start();
|
||||
} else
|
||||
throw exception("SetThreadPriority was called on a non-KThread object");
|
||||
}
|
||||
|
||||
void CloseHandle(device_state &state) {
|
||||
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
|
||||
switch (object->type) {
|
||||
case (type::KObjectType::KThread):
|
||||
state.os->KillThread(std::static_pointer_cast<type::KThread>(object)->pid);
|
||||
break;
|
||||
case (type::KObjectType::KProcess):
|
||||
state.os->KillThread(std::static_pointer_cast<type::KProcess>(object)->main_thread);
|
||||
break;
|
||||
default:
|
||||
state.nce->SetRegister(wreg::w0, constant::status::inv_handle);
|
||||
return;
|
||||
}
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
}
|
||||
|
||||
void ConnectToNamedPort(device_state &state) {
|
||||
char port[constant::port_size]{0};
|
||||
state.os->this_process->ReadMemory(port, state.nce->GetRegister(xreg::x1), constant::port_size);
|
||||
if (std::strcmp(port, "sm:") == 0)
|
||||
state.nce->SetRegister(wreg::w1, constant::sm_handle);
|
||||
else
|
||||
throw exception(fmt::format("svcConnectToNamedPort tried connecting to invalid port: \"{}\"", port));
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
}
|
||||
|
||||
void SendSyncRequest(device_state &state) {
|
||||
state.logger->Write(Logger::DEBUG, "----------------------------svcSendSyncRequest Start-----------------------");
|
||||
state.logger->Write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{:X}.", state.nce->GetRegister(xreg::x0));
|
||||
state.os->IpcHandler(static_cast<handle_t>(state.nce->GetRegister(xreg::x0)));
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
state.nce->SetRegister(wreg::w19, constant::status::success);
|
||||
state.logger->Write(Logger::DEBUG, "----------------------------svcSendSyncRequest End-------------------------");
|
||||
}
|
||||
|
||||
void OutputDebugString(device_state &state) {
|
||||
std::string debug(state.nce->GetRegister(xreg::x1), '\0');
|
||||
state.os->this_process->ReadMemory((void *) debug.data(), state.nce->GetRegister(xreg::x0), state.nce->GetRegister(xreg::x1));
|
||||
state.logger->Write(Logger::INFO, "svcOutputDebugString: {}", debug.c_str());
|
||||
state.nce->SetRegister(wreg::w0, 0);
|
||||
}
|
||||
|
||||
void GetInfo(device_state &state) {
|
||||
state.logger->Write(Logger::DEBUG, "svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(wreg::w1), state.nce->GetRegister(xreg::x3));
|
||||
switch (state.nce->GetRegister(wreg::w1)) {
|
||||
case constant::infoState::AllowedCpuIdBitmask:
|
||||
case constant::infoState::AllowedThreadPriorityMask:
|
||||
case constant::infoState::IsCurrentProcessBeingDebugged:
|
||||
case constant::infoState::TitleId:
|
||||
case constant::infoState::PrivilegedProcessId:
|
||||
state.nce->SetRegister(xreg::x1, 0);
|
||||
break;
|
||||
case constant::infoState::AliasRegionBaseAddr:
|
||||
state.nce->SetRegister(xreg::x1, constant::map_addr);
|
||||
break;
|
||||
case constant::infoState::AliasRegionSize:
|
||||
state.nce->SetRegister(xreg::x1, constant::map_size);
|
||||
break;
|
||||
case constant::infoState::HeapRegionBaseAddr:
|
||||
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address);
|
||||
break;
|
||||
case constant::infoState::HeapRegionSize:
|
||||
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->size);
|
||||
break;
|
||||
case constant::infoState::TotalMemoryAvailable:
|
||||
state.nce->SetRegister(xreg::x1, constant::total_phy_mem);
|
||||
break;
|
||||
case constant::infoState::TotalMemoryUsage:
|
||||
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz + state.nce->GetSharedSize());
|
||||
break;
|
||||
case constant::infoState::AddressSpaceBaseAddr:
|
||||
state.nce->SetRegister(xreg::x1, constant::base_addr);
|
||||
break;
|
||||
case constant::infoState::AddressSpaceSize:
|
||||
state.nce->SetRegister(xreg::x1, constant::base_size);
|
||||
break;
|
||||
case constant::infoState::StackRegionBaseAddr:
|
||||
state.nce->SetRegister(xreg::x1, state.this_thread->stack_top);
|
||||
break;
|
||||
case constant::infoState::StackRegionSize:
|
||||
state.nce->SetRegister(xreg::x1, state.this_process->main_thread_stack_sz);
|
||||
break;
|
||||
case constant::infoState::PersonalMmHeapSize:
|
||||
state.nce->SetRegister(xreg::x1, constant::total_phy_mem);
|
||||
break;
|
||||
case constant::infoState::PersonalMmHeapUsage:
|
||||
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz);
|
||||
break;
|
||||
case constant::infoState::TotalMemoryAvailableWithoutMmHeap:
|
||||
state.nce->SetRegister(xreg::x1, constant::total_phy_mem); // TODO: NPDM specifies SystemResourceSize, subtract that from this
|
||||
break;
|
||||
case constant::infoState::TotalMemoryUsedWithoutMmHeap:
|
||||
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz); // TODO: Same as above
|
||||
break;
|
||||
case constant::infoState::UserExceptionContextAddr:
|
||||
state.nce->SetRegister(xreg::x1, state.this_process->tls_pages[0]->Get(0));
|
||||
break;
|
||||
default:
|
||||
state.logger->Write(Logger::WARN, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(wreg::w1), state.nce->GetRegister(xreg::x3));
|
||||
state.nce->SetRegister(wreg::w0, constant::status::unimpl);
|
||||
return;
|
||||
}
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
}
|
||||
|
||||
void ExitProcess(device_state &state) {
|
||||
state.os->KillThread(state.this_process->main_thread);
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../common.h"
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
enum class KObjectType {
|
||||
KThread, KProcess, KSharedMemory
|
||||
};
|
||||
|
||||
class KObject {
|
||||
public:
|
||||
u32 handle;
|
||||
KObjectType type;
|
||||
|
||||
KObject(handle_t handle, KObjectType type) : handle(handle), type(type) {}
|
||||
};
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
#include "KPrivateMemory.h"
|
||||
#include "../../nce.h"
|
||||
#include "../../os.h"
|
||||
#include <android/sharedmem.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
u64 MapPrivateFunc(u64 dst_address, u64 src_address, size_t size, u64 perms) {
|
||||
dst_address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(dst_address), size, static_cast<int>(perms), MAP_PRIVATE | MAP_ANONYMOUS | ((dst_address) ? MAP_FIXED : 0), -1, 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
if (src_address) {
|
||||
memcpy(reinterpret_cast<void *>(dst_address), reinterpret_cast<const void *>(src_address), size);
|
||||
mprotect(reinterpret_cast<void *>(src_address), size, PROT_NONE);
|
||||
}
|
||||
return dst_address;
|
||||
}
|
||||
|
||||
KPrivateMemory::KPrivateMemory(const device_state &state, u64 dst_address, u64 src_address, size_t size, Memory::Permission permission, const Memory::Type type, pid_t owner_pid) : state(state), address(dst_address), size(size), permission(permission), type(type), owner_pid(owner_pid) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = dst_address;
|
||||
fregs.regs[1] = src_address;
|
||||
fregs.regs[2] = size;
|
||||
fregs.regs[3] = static_cast<u64>(permission.get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapPrivateFunc), fregs, owner_pid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while mapping private region in child process");
|
||||
if (!this->address) this->address = fregs.regs[0];
|
||||
}
|
||||
|
||||
u64 UnmapPrivateFunc(u64 address, size_t size) {
|
||||
return static_cast<u64>(munmap(reinterpret_cast<void *>(address), size));
|
||||
}
|
||||
|
||||
u64 RemapPrivateFunc(u64 address, size_t old_size, size_t size) {
|
||||
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
|
||||
}
|
||||
|
||||
void KPrivateMemory::Resize(size_t new_size) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = new_size;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, owner_pid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while remapping private region in child process");
|
||||
size = new_size;
|
||||
}
|
||||
|
||||
u64 UpdatePermissionPrivateFunc(u64 address, size_t size, u64 perms) {
|
||||
return static_cast<u64>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
|
||||
}
|
||||
|
||||
void KPrivateMemory::UpdatePermission(Memory::Permission new_perms) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64>(new_perms.get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, owner_pid);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
throw exception("An error occurred while updating private region's permissions in child process");
|
||||
permission = new_perms;
|
||||
}
|
||||
|
||||
Memory::MemoryInfo KPrivateMemory::GetInfo() {
|
||||
Memory::MemoryInfo info{};
|
||||
info.base_address = address;
|
||||
info.size = size;
|
||||
info.type = static_cast<u64>(type);
|
||||
info.memory_attribute.IsIpcLocked = (info.ipc_ref_count > 0);
|
||||
info.memory_attribute.IsDeviceShared = (info.device_ref_count > 0);
|
||||
info.perms = permission;
|
||||
info.ipc_ref_count = ipc_ref_count;
|
||||
info.device_ref_count = device_ref_count;
|
||||
return info;
|
||||
}
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../memory.h"
|
||||
#include "KObject.h"
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
class KPrivateMemory {
|
||||
private:
|
||||
const device_state &state; //!< The state of the device
|
||||
|
||||
public:
|
||||
u64 address; //!< The address of the allocated memory
|
||||
size_t size; //!< The size of the allocated memory
|
||||
u16 ipc_ref_count{}; //!< The amount of reference to this memory for IPC
|
||||
u16 device_ref_count{}; //!< The amount of reference to this memory for IPC
|
||||
Memory::Permission permission; //!< The amount of reference to this memory for IPC
|
||||
const Memory::Type type; //!< The type of this memory allocation
|
||||
const pid_t owner_pid; //!< The PID of the owner process
|
||||
|
||||
/**
|
||||
* Constructor of a private memory object
|
||||
* @param dst_address The address to map to (If NULL then an arbitrary address is picked)
|
||||
* @param src_address The address to map from (If NULL then no copy is performed)
|
||||
* @param size The size of the allocation
|
||||
* @param permission The permissions for the memory
|
||||
* @param owner_pid The PID of the owner process
|
||||
*/
|
||||
KPrivateMemory(const device_state &state, u64 dst_address, u64 src_address, size_t size, Memory::Permission permission, const Memory::Type type, const pid_t owner_pid);
|
||||
|
||||
/**
|
||||
* Remap a chunk of memory as to change the size occupied by it
|
||||
* @param address The address of the mapped memory
|
||||
* @param old_size The current size of the memory
|
||||
* @param size The new size of the memory
|
||||
*/
|
||||
void Resize(size_t new_size);
|
||||
|
||||
/**
|
||||
* Updates the permissions of a chunk of mapped memory
|
||||
* @param perms The new permissions to be set for the memory
|
||||
*/
|
||||
void UpdatePermission(Memory::Permission new_perms);
|
||||
|
||||
/**
|
||||
* @param pid The PID of the requesting process
|
||||
* @return A Memory::MemoryInfo struct based on attributes of the memory
|
||||
*/
|
||||
Memory::MemoryInfo GetInfo();
|
||||
};
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
#include "KProcess.h"
|
||||
#include "../../nce.h"
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
KProcess::tls_page_t::tls_page_t(u64 address) : address(address) {}
|
||||
|
||||
u64 KProcess::tls_page_t::ReserveSlot() {
|
||||
if (Full())
|
||||
throw exception("Trying to get TLS slot from full page");
|
||||
slot[index] = true;
|
||||
return Get(index++); // ++ on right will cause increment after evaluation of expression
|
||||
}
|
||||
|
||||
u64 KProcess::tls_page_t::Get(u8 slot_no) {
|
||||
if (slot_no >= constant::tls_slots)
|
||||
throw exception("TLS slot is out of range");
|
||||
return address + (constant::tls_slot_size * slot_no);
|
||||
}
|
||||
|
||||
bool KProcess::tls_page_t::Full() {
|
||||
return slot[constant::tls_slots - 1];
|
||||
}
|
||||
|
||||
u64 KProcess::GetTLSSlot(bool init) {
|
||||
if (!init)
|
||||
for (auto &tls_page: tls_pages) {
|
||||
if (!tls_page->Full())
|
||||
return tls_page->ReserveSlot();
|
||||
}
|
||||
auto tls_mem = std::make_shared<KPrivateMemory>(KPrivateMemory(state, 0, 0, PAGE_SIZE, {true, true, false}, Memory::Type::ThreadLocal, main_thread));
|
||||
memory_map[tls_mem->address] = tls_mem;
|
||||
tls_pages.push_back(std::make_shared<tls_page_t>(tls_mem->address));
|
||||
auto &tls_page = tls_pages.back();
|
||||
if (init)
|
||||
tls_page->ReserveSlot(); // User-mode exception handling
|
||||
return tls_page->ReserveSlot();
|
||||
}
|
||||
|
||||
KProcess::KProcess(pid_t pid, u64 entry_point, u64 stack_base, u64 stack_size, const device_state &state, handle_t handle) : state(state), handle(handle), main_thread_stack_sz(stack_size), KObject(handle, KObjectType::KProcess) {
|
||||
process_state = process_state_t::Created;
|
||||
main_thread = pid;
|
||||
state.nce->WaitRdy(pid);
|
||||
thread_map[main_thread] = std::make_shared<KThread>(handle_index, pid, entry_point, 0, stack_base + stack_size, GetTLSSlot(true), constant::default_priority, this, state);
|
||||
NewHandle(std::static_pointer_cast<KObject>(thread_map[main_thread]));
|
||||
MapPrivateRegion(0, constant::def_heap_size, {true, true, true}, Memory::Type::Heap, Memory::Region::heap);
|
||||
for (auto ®ion : state.nce->memory_map) {
|
||||
region.second->InitiateProcess(pid);
|
||||
}
|
||||
mem_fd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
|
||||
if (mem_fd == -1) throw exception(fmt::format("Cannot open file descriptor to /proc/{}/mem", pid));
|
||||
}
|
||||
|
||||
KProcess::~KProcess() {
|
||||
close(mem_fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function executed by all child threads after cloning
|
||||
*/
|
||||
int ExecuteChild(void *) {
|
||||
ptrace(PTRACE_TRACEME);
|
||||
asm volatile("brk #0xFF"); // BRK #constant::brk_rdy (So we know when the thread/process is ready)
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 CreateThreadFunc(u64 stack_top) {
|
||||
pid_t pid = clone(&ExecuteChild, reinterpret_cast<void *>(stack_top), CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM, nullptr); // NOLINT(hicpp-signed-bitwise)
|
||||
return static_cast<u64>(pid);
|
||||
}
|
||||
|
||||
std::shared_ptr<KThread> KProcess::CreateThread(u64 entry_point, u64 entry_arg, u64 stack_top, u8 priority) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = entry_point;
|
||||
fregs.regs[1] = stack_top;
|
||||
state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, main_thread);
|
||||
if (fregs.regs[0] == -1)
|
||||
throw exception(fmt::format("Cannot create thread: Address: {}, Stack Top: {}", entry_point, stack_top));
|
||||
auto thread = std::make_shared<kernel::type::KThread>(handle_index, static_cast<pid_t>(fregs.regs[0]), entry_point, entry_arg, stack_top, GetTLSSlot(false), priority, this, state);
|
||||
NewHandle(std::static_pointer_cast<KObject>(thread));
|
||||
return thread;
|
||||
}
|
||||
|
||||
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
|
||||
pread64(mem_fd, destination, size, offset);
|
||||
}
|
||||
|
||||
void KProcess::WriteMemory(void *source, u64 offset, size_t size) const {
|
||||
pwrite64(mem_fd, source, size, offset);
|
||||
}
|
||||
|
||||
std::shared_ptr<KPrivateMemory> KProcess::MapPrivateRegion(u64 address, size_t size, const Memory::Permission perms, const Memory::Type type, const Memory::Region region) {
|
||||
auto item = std::make_shared<KPrivateMemory>(state, address, 0, size, perms, type, main_thread);
|
||||
memory_map[item->address] = item;
|
||||
memory_region_map[region] = item;
|
||||
return item;
|
||||
}
|
||||
|
||||
handle_t KProcess::NewHandle(std::shared_ptr<KObject> obj) {
|
||||
handle_table[handle_index] = std::move(obj);
|
||||
state.logger->Write(Logger::DEBUG, "Creating handle index 0x{0:X}", handle_index);
|
||||
return handle_index++; // Increment value after return
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "KThread.h"
|
||||
#include "KPrivateMemory.h"
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
/**
|
||||
* The KProcess class is responsible for holding the state of a process
|
||||
*/
|
||||
class KProcess : public KObject {
|
||||
private:
|
||||
/**
|
||||
* tls_page_t holds the status of a single TLS page (A page is 4096 bytes on ARMv8).
|
||||
* Each TLS page has 8 slots, each 0x200 (512) bytes in size.
|
||||
* The first slot of the first page is reserved for user-mode exception handling
|
||||
* Read more about TLS here: https://switchbrew.org/wiki/Thread_Local_Storage
|
||||
*/
|
||||
struct tls_page_t {
|
||||
u64 address; //!< The address of the page allocated for TLS
|
||||
u8 index = 0; //!< The slots are assigned sequentially, this holds the index of the last TLS slot reserved
|
||||
bool slot[constant::tls_slots]{0}; //!< An array of booleans denoting which TLS slots are reserved
|
||||
|
||||
/**
|
||||
* @param address The address of the allocated page
|
||||
*/
|
||||
tls_page_t(u64 address);
|
||||
|
||||
/**
|
||||
* Reserves a single 0x200 byte TLS slot
|
||||
* @return The address of the reserved slot
|
||||
*/
|
||||
u64 ReserveSlot();
|
||||
|
||||
/**
|
||||
* Returns the address of a particular slot
|
||||
* @param slot_no The number of the slot to be returned
|
||||
* @return The address of the specified slot
|
||||
*/
|
||||
u64 Get(u8 slot_no);
|
||||
|
||||
/**
|
||||
* @return If the whole page is full or not
|
||||
*/
|
||||
bool Full();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param init If this initializes the first page (As the first TLS slot is reserved)
|
||||
* @return The address of a free TLS slot
|
||||
*/
|
||||
u64 GetTLSSlot(bool init);
|
||||
|
||||
int mem_fd; //!< The file descriptor to the memory of the process
|
||||
const device_state &state; //!< The state of the device
|
||||
|
||||
public:
|
||||
enum class process_state_t { Created, CreatedAttached, Started, Crashed, StartedAttached, Exiting, Exited, DebugSuspended } process_state; //!< The state of the process
|
||||
handle_t handle; //!< The handle of the current process in it's parent process's handle table (Will be 0 if this is the main process)
|
||||
handle_t handle_index = constant::base_handle_index; //!< This is used to keep track of what to map as an handle
|
||||
pid_t main_thread; //!< The PID of the main thread
|
||||
size_t main_thread_stack_sz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se)
|
||||
std::map<u64, std::shared_ptr<KPrivateMemory>> memory_map; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory
|
||||
std::map<Memory::Region, std::shared_ptr<KPrivateMemory>> memory_region_map; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory
|
||||
std::map<handle_t, std::shared_ptr<KObject>> handle_table; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
|
||||
std::map<pid_t, std::shared_ptr<KThread>> thread_map; //!< A mapping from a PID to it's corresponding KThread object
|
||||
std::vector<std::shared_ptr<tls_page_t>> tls_pages; //!< A vector of all allocated TLS pages
|
||||
|
||||
/**
|
||||
* Creates a KThread object for the main thread and opens the process's memory file
|
||||
* @param pid The PID of the main thread
|
||||
* @param entry_point The address to start execution at
|
||||
* @param stack_base The base of the stack
|
||||
* @param stack_size The size of the stack
|
||||
* @param state The state of the device
|
||||
* @param handle A handle to the process, this isn't used if the kernel creates the process
|
||||
*/
|
||||
KProcess(pid_t pid, u64 entry_point, u64 stack_base, u64 stack_size, const device_state &state, handle_t handle = 0);
|
||||
|
||||
/**
|
||||
* Close the file descriptor to the process's memory
|
||||
*/
|
||||
~KProcess();
|
||||
|
||||
/**
|
||||
* Create a thread in this process
|
||||
* @param entry_point The address of the initial function
|
||||
* @param entry_arg An argument to the function
|
||||
* @param stack_top The top of the stack
|
||||
* @param priority The priority of the thread
|
||||
* @return An instance of KThread class for the corresponding thread
|
||||
*/
|
||||
std::shared_ptr<KThread> CreateThread(u64 entry_point, u64 entry_arg, u64 stack_top, u8 priority);
|
||||
|
||||
/**
|
||||
* Returns an object of type T from process memory
|
||||
* @tparam T The type of the object to be read
|
||||
* @param address The address of the object
|
||||
* @return An object of type T with read data
|
||||
*/
|
||||
template<typename T>
|
||||
T ReadMemory(u64 address) const {
|
||||
T item{};
|
||||
ReadMemory(&item, address, sizeof(T));
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an object of type T to process memory
|
||||
* @tparam T The type of the object to be written
|
||||
* @param item The object to write
|
||||
* @param address The address of the object
|
||||
*/
|
||||
template<typename T>
|
||||
void WriteMemory(T &item, u64 address) const {
|
||||
WriteMemory(&item, address, sizeof(T));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a piece of process memory
|
||||
* @param destination The address to the location where the process memory is written
|
||||
* @param offset The address to read from in process memory
|
||||
* @param size The amount of memory to be read
|
||||
*/
|
||||
void ReadMemory(void *destination, u64 offset, size_t size) const;
|
||||
|
||||
/**
|
||||
* Write a piece of process memory
|
||||
* @param source The address of where the data to be written is present
|
||||
* @param offset The address to write to in process memory
|
||||
* @param size The amount of memory to be written
|
||||
*/
|
||||
void WriteMemory(void *source, u64 offset, size_t size) const;
|
||||
|
||||
/**
|
||||
* Map a chunk of process local memory (private memory)
|
||||
* @param address The address to map to (Can be 0 if address doesn't matter)
|
||||
* @param size The size of the chunk of memory
|
||||
* @param perms The permissions of the memory
|
||||
* @param type The type of the memory
|
||||
* @param region The specific region this memory is mapped for
|
||||
* @return The address of the mapped chunk (Use when address is 0)
|
||||
*/
|
||||
std::shared_ptr<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const Memory::Permission perms, const Memory::Type type, const Memory::Region region);
|
||||
|
||||
/**
|
||||
* Creates a new handle to a KObject and adds it to the process handle_table
|
||||
* @param obj A shared pointer to the KObject to be added to the table
|
||||
* @return The handle of the corresponding object
|
||||
*/
|
||||
handle_t NewHandle(std::shared_ptr<KObject> obj);
|
||||
};
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../memory.h"
|
||||
#include "KObject.h"
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
class KSharedMemory : public KObject {
|
||||
private:
|
||||
const device_state &state; //!< The state of the device
|
||||
int fd; //!< A file descriptor to the underlying shared memory
|
||||
|
||||
public:
|
||||
u64 address; //!< The address of the allocated memory
|
||||
size_t size; //!< The size of the allocated memory
|
||||
u16 ipc_ref_count{}; //!< The amount of reference to this memory for IPC
|
||||
u16 device_ref_count{}; //!< The amount of reference to this memory for IPC
|
||||
Memory::Permission local_permission; //!< The amount of reference to this memory for IPC
|
||||
Memory::Permission remote_permission; //!< The permission of any process except the owner process
|
||||
Memory::Type type; //!< The type of this memory allocation
|
||||
pid_t owner_pid; //!< The PID of the owner process, 0 means memory is owned by kernel process
|
||||
|
||||
/**
|
||||
* Constructor of a shared memory object
|
||||
* @param size The size of the allocation
|
||||
* @param local_permission The permission of the owner process
|
||||
* @param remote_permission The permission of any process except the owner process
|
||||
* @param owner_pid The PID of the owner process, 0 means memory is owned by kernel process
|
||||
*/
|
||||
KSharedMemory(const device_state &state, size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, Memory::Type type, handle_t handle, pid_t owner_pid = 0);
|
||||
|
||||
/**
|
||||
* Maps the shared memory at an address
|
||||
* @param address The address to map to (If NULL an arbitrary address is picked)
|
||||
*/
|
||||
void Map(u64 address);
|
||||
|
||||
/**
|
||||
* Destructor of shared memory, it deallocates the memory from all processes
|
||||
*/
|
||||
~KSharedMemory();
|
||||
|
||||
/**
|
||||
* Resize a chunk of memory as to change the size occupied by it
|
||||
* @param new_size The new size of the memory
|
||||
*/
|
||||
void Resize(size_t new_size);
|
||||
|
||||
/**
|
||||
* Updates the permissions of a chunk of mapped memory
|
||||
* @param local If true change local permissions else change remote permissions
|
||||
* @param perms The new permissions to be set for the memory
|
||||
*/
|
||||
void UpdatePermission(bool local, Memory::Permission new_perms);
|
||||
|
||||
/**
|
||||
* Initiates the instance of shared memory in a particular process
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void InitiateProcess(pid_t pid);
|
||||
|
||||
/**
|
||||
* @param pid The PID of the requesting process
|
||||
* @return A Memory::MemoryInfo struct based on attributes of the memory
|
||||
*/
|
||||
Memory::MemoryInfo GetInfo(pid_t pid);
|
||||
};
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
#include <sys/resource.h>
|
||||
#include "KThread.h"
|
||||
#include "KProcess.h"
|
||||
#include "../../nce.h"
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
KThread::KThread(handle_t handle, pid_t pid, u64 entry_point, u64 entry_arg, u64 stack_top, u64 tls, u8 priority, KProcess *parent, const device_state &state) : handle(handle), pid(pid), entry_point(entry_point), entry_arg(entry_arg), stack_top(stack_top), tls(tls), priority(priority), parent(parent), state(state), KObject(handle, KObjectType::KThread) {
|
||||
UpdatePriority(priority);
|
||||
}
|
||||
|
||||
KThread::~KThread() {
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
|
||||
void KThread::Start() {
|
||||
if (pid == parent->main_thread) parent->process_state = KProcess::process_state_t::Started;
|
||||
state.nce->StartProcess(entry_point, entry_arg, stack_top, handle, pid);
|
||||
}
|
||||
|
||||
void KThread::UpdatePriority(u8 priority) {
|
||||
this->priority = priority;
|
||||
auto li_priority = static_cast<int8_t>(constant::priority_an.first + ((static_cast<float>(constant::priority_an.second - constant::priority_an.first) / static_cast<float>(constant::priority_nin.second - constant::priority_nin.first)) * (static_cast<float>(priority) - constant::priority_nin.first))); // Resize range priority_nin (Nintendo Priority) to priority_an (Android Priority)
|
||||
if (setpriority(PRIO_PROCESS, static_cast<id_t>(pid), li_priority) == -1)
|
||||
throw exception(fmt::format("Couldn't set process priority to {} for PID: {}", li_priority, pid));
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "KObject.h"
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
/**
|
||||
* KThread class is responsible for holding the state of a thread
|
||||
*/
|
||||
class KThread : public KObject {
|
||||
private:
|
||||
KProcess *parent; //!< The parent process of this thread
|
||||
const device_state &state; //!< The state of the device
|
||||
u64 entry_point; //!< The address to start execution at
|
||||
u64 entry_arg; //!< An argument to pass to the process on entry
|
||||
|
||||
public:
|
||||
handle_t handle; //!< The handle of the current thread in it's parent process's handle table
|
||||
pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level])
|
||||
u64 stack_top; //!< The top of the stack (Where it starts growing downwards from)
|
||||
u64 tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread
|
||||
u8 priority; //!< Hold the priority of a thread in Nintendo format
|
||||
|
||||
/**
|
||||
* @param handle The handle of the current thread
|
||||
* @param pid The PID of the current thread
|
||||
* @param entry_point The address to start execution at
|
||||
* @param entry_arg An argument to pass to the process on entry
|
||||
* @param stack_top The top of the stack
|
||||
* @param tls The address of the TLS slot assigned
|
||||
* @param priority The priority of the thread in Nintendo format
|
||||
* @param parent The parent process of this thread
|
||||
* @param arg An optional argument to pass to the process
|
||||
*/
|
||||
KThread(handle_t handle, pid_t pid, u64 entry_point, u64 entry_arg, u64 stack_top, u64 tls, u8 priority, KProcess *parent, const device_state &state);
|
||||
|
||||
/**
|
||||
* Kills the thread and deallocates the memory allocated for stack.
|
||||
*/
|
||||
~KThread();
|
||||
|
||||
/**
|
||||
* Starts the current thread
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* Set the priority of the current thread to `priority` using setpriority [https://linux.die.net/man/3/setpriority].
|
||||
* We rescale the priority from Nintendo scale to that of Android.
|
||||
* @param priority The priority of the thread in Nintendo format
|
||||
*/
|
||||
void UpdatePriority(u8 priority);
|
||||
};
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
#include <vector>
|
||||
#include "nro.h"
|
||||
|
||||
namespace lightSwitch::loader {
|
||||
NroLoader::NroLoader(std::string file_path, const device_state &state) : Loader(file_path) {
|
||||
NroHeader header{};
|
||||
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
|
||||
if (header.magic != constant::nro_magic)
|
||||
throw exception(fmt::format("Invalid NRO magic! 0x{0:X}", header.magic));
|
||||
|
||||
state.nce->MapSharedRegion(constant::base_addr, header.text.size, {true, true, true}, {true, true, true}, Memory::Type::CodeStatic, Memory::Region::text); // R-X
|
||||
state.logger->Write(Logger::DEBUG, "Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", constant::base_addr, header.text.size);
|
||||
|
||||
auto rodata = state.nce->MapSharedRegion(constant::base_addr + header.text.size, header.ro.size, {true, true, false}, {true, false, false}, Memory::Type::CodeReadOnly, Memory::Region::rodata); // R--
|
||||
state.logger->Write(Logger::DEBUG, "Successfully mapped region .ro @ 0x{0:X}, Size = 0x{1:X}", constant::base_addr + header.text.size, header.ro.size);
|
||||
|
||||
state.nce->MapSharedRegion(constant::base_addr + header.text.size + header.ro.size, header.data.size, {true, true, false}, {true, true, false}, Memory::Type::CodeStatic, Memory::Region::data); // RW-
|
||||
state.logger->Write(Logger::DEBUG, "Successfully mapped region .data @ 0x{0:X}, Size = 0x{1:X}", constant::base_addr + header.text.size + header.ro.size, header.data.size);
|
||||
|
||||
state.nce->MapSharedRegion(constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize, {true, true, true}, {true, true, true}, Memory::Type::CodeMutable, Memory::Region::bss); // RWX
|
||||
state.logger->Write(Logger::DEBUG, "Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}", constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize);
|
||||
|
||||
ReadOffset(reinterpret_cast<u8 *>(constant::base_addr), header.text.offset, header.text.size);
|
||||
ReadOffset(reinterpret_cast<u8 *>(constant::base_addr + header.text.size), header.ro.offset, header.ro.size);
|
||||
ReadOffset(reinterpret_cast<u8 *>(constant::base_addr + header.text.size + header.ro.size), header.data.offset, header.data.size);
|
||||
|
||||
|
||||
// Replace SVC & MRS with BRK
|
||||
auto address = (u32 *) constant::base_addr + header.text.offset;
|
||||
size_t text_size = header.text.size / sizeof(u32);
|
||||
for (size_t iter = 0; iter < text_size; iter++) {
|
||||
auto instr_svc = reinterpret_cast<instr::svc *>(address + iter);
|
||||
auto instr_mrs = reinterpret_cast<instr::mrs *>(address + iter);
|
||||
|
||||
if (instr_svc->verify()) {
|
||||
instr::brk brk(static_cast<u16>(instr_svc->value));
|
||||
address[iter] = *reinterpret_cast<u32 *>(&brk);
|
||||
} else if (instr_mrs->verify() && instr_mrs->src_reg == constant::tpidrro_el0) {
|
||||
instr::brk brk(static_cast<u16>(constant::svc_last + 1 + instr_mrs->dst_reg));
|
||||
address[iter] = *reinterpret_cast<u32 *>(&brk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
#include <sched.h>
|
||||
#include <linux/uio.h>
|
||||
#include <linux/elf.h>
|
||||
#include "os.h"
|
||||
#include "nce.h"
|
||||
|
||||
extern bool halt;
|
||||
|
||||
namespace lightSwitch {
|
||||
void NCE::ReadRegisters(user_pt_regs ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, sizeof(registers)};
|
||||
long status = ptrace(PTRACE_GETREGSET, pid ? pid : curr_pid, NT_PRSTATUS, &iov);
|
||||
if (status == -1) throw exception(fmt::format("Cannot read registers, PID: {}, Error: {}", pid, strerror(errno)));
|
||||
}
|
||||
|
||||
void NCE::WriteRegisters(user_pt_regs ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, sizeof(registers)};
|
||||
long status = ptrace(PTRACE_SETREGSET, pid ? pid : curr_pid, NT_PRSTATUS, &iov);
|
||||
if (status == -1) throw exception(fmt::format("Cannot write registers, PID: {}, Error: {}", pid, strerror(errno)));
|
||||
}
|
||||
|
||||
instr::brk NCE::ReadBrk(u64 address, pid_t pid) const {
|
||||
long status = ptrace(PTRACE_PEEKDATA, pid ? pid : curr_pid, address, NULL);
|
||||
if (status == -1) throw exception(fmt::format("Cannot read instruction from memory, Address: {}, PID: {}, Error: {}", address, pid, strerror(errno)));
|
||||
return *(reinterpret_cast<instr::brk *>(&status));
|
||||
}
|
||||
|
||||
void NCE::Initialize(const device_state &state) {
|
||||
this->state = &state;
|
||||
}
|
||||
|
||||
void NCE::Execute() {
|
||||
int status = 0;
|
||||
while (!halt && !state->os->process_map.empty() && ((curr_pid = wait(&status)) != -1)) {
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
|
||||
auto &curr_regs = register_map[curr_pid];
|
||||
ReadRegisters(curr_regs);
|
||||
auto instr = ReadBrk(curr_regs.pc);
|
||||
if (instr.verify()) {
|
||||
// We store the instruction value as the immediate value in BRK. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0.
|
||||
if (instr.value <= constant::svc_last) {
|
||||
state->os->SvcHandler(static_cast<u16>(instr.value), curr_pid);
|
||||
} else if (instr.value > constant::svc_last && instr.value <= constant::svc_last + constant::num_regs) {
|
||||
// Catch MRS that reads the value of TPIDRRO_EL0 (TLS)
|
||||
SetRegister(static_cast<xreg>(instr.value - (constant::svc_last + 1)), state->os->process_map.at(curr_pid)->thread_map.at(curr_pid)->tls);
|
||||
state->logger->Write(Logger::DEBUG, "\"MRS X{}, TPIDRRO_EL0\" has been called", instr.value - (constant::svc_last + 1));
|
||||
} else if (instr.value == constant::brk_rdy)
|
||||
continue;
|
||||
else
|
||||
throw exception(fmt::format("Received unhandled BRK: 0x{:X}", static_cast<u64>(instr.value)));
|
||||
}
|
||||
curr_regs.pc += 4; // Increment program counter by a single instruction (32 bits)
|
||||
WriteRegisters(curr_regs);
|
||||
} else {
|
||||
state->logger->Write(Logger::DEBUG, "Thread threw unknown signal, PID: {}, Stop Signal: {}", curr_pid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
|
||||
state->os->KillThread(curr_pid);
|
||||
}
|
||||
ResumeProcess();
|
||||
}
|
||||
}
|
||||
|
||||
void brk_lr() {
|
||||
asm("BRK #0xFF"); // BRK #constant::brk_rdy
|
||||
}
|
||||
|
||||
void NCE::ExecuteFunction(void *func, user_pt_regs &func_regs, pid_t pid) {
|
||||
pid = pid ? pid : curr_pid;
|
||||
bool was_running = PauseProcess(pid);
|
||||
user_pt_regs backup_regs{};
|
||||
ReadRegisters(backup_regs, pid);
|
||||
func_regs.pc = reinterpret_cast<u64>(func);
|
||||
func_regs.sp = backup_regs.sp;
|
||||
func_regs.regs[static_cast<uint>(xreg::x30)] = reinterpret_cast<u64>(brk_lr); // Set LR to 'brk_lr' so the application will hit a breakpoint after the function returns [LR is where the program goes after it returns from a function]
|
||||
WriteRegisters(func_regs, pid);
|
||||
ResumeProcess(pid);
|
||||
func_regs = WaitRdy(pid);
|
||||
WriteRegisters(backup_regs, pid);
|
||||
if (was_running)
|
||||
ResumeProcess(pid);
|
||||
}
|
||||
|
||||
user_pt_regs NCE::WaitRdy(pid_t pid) {
|
||||
int status;
|
||||
user_pt_regs regs{};
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) { // NOLINT(hicpp-signed-bitwise)
|
||||
ReadRegisters(regs, pid);
|
||||
auto instr = ReadBrk(regs.pc, pid);
|
||||
if (instr.verify() && instr.value == constant::brk_rdy) {
|
||||
regs.pc += 4; // Increment program counter by a single instruction (32 bits)
|
||||
WriteRegisters(regs, pid);
|
||||
return regs;
|
||||
} else
|
||||
throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<u64>(instr.value)));
|
||||
} else
|
||||
throw exception(fmt::format("An unknown signal was caused during WaitRdy, PID: {}, Status: 0x{:X}, Signal: {}", pid, status, strsignal(WSTOPSIG(status)))); // NOLINT(hicpp-signed-bitwise)
|
||||
}
|
||||
|
||||
bool NCE::PauseProcess(pid_t pid) const {
|
||||
pid = pid ? pid : curr_pid;
|
||||
int status = 0;
|
||||
waitpid(pid, &status, WNOHANG);
|
||||
bool was_stopped = WIFSTOPPED(status); // NOLINT(hicpp-signed-bitwise)
|
||||
if (was_stopped) {
|
||||
if ((kill(pid, SIGSTOP) != -1) && (waitpid(pid, nullptr, 0) != -1)) return true;
|
||||
else throw exception(fmt::format("Cannot pause process: {}, Error: {}", pid, strerror(errno)));
|
||||
} else return false;
|
||||
}
|
||||
|
||||
void NCE::ResumeProcess(pid_t pid) const {
|
||||
long status = ptrace(PTRACE_CONT, pid ? pid : curr_pid, NULL, NULL);
|
||||
if (status == -1) throw exception(fmt::format("Cannot resume process: {}, Error: {}", pid, strerror(errno)));
|
||||
}
|
||||
|
||||
void NCE::StartProcess(u64 entry_point, u64 entry_arg, u64 stack_top, u32 handle, pid_t pid) const {
|
||||
user_pt_regs regs{0};
|
||||
regs.sp = stack_top;
|
||||
regs.pc = entry_point;
|
||||
regs.regs[0] = entry_arg;
|
||||
regs.regs[1] = handle;
|
||||
WriteRegisters(regs, pid);
|
||||
ResumeProcess(pid);
|
||||
}
|
||||
|
||||
u64 NCE::GetRegister(xreg reg_id, pid_t pid) {
|
||||
return register_map.at(pid ? pid : curr_pid).regs[static_cast<uint>(reg_id)];
|
||||
}
|
||||
|
||||
void NCE::SetRegister(xreg reg_id, u64 value, pid_t pid) {
|
||||
register_map.at(pid ? pid : curr_pid).regs[static_cast<uint>(reg_id)] = value;
|
||||
}
|
||||
|
||||
u64 NCE::GetRegister(wreg reg_id, pid_t pid) {
|
||||
return (reinterpret_cast<u32 *>(®ister_map.at(pid ? pid : curr_pid).regs))[static_cast<uint>(reg_id) * 2];
|
||||
}
|
||||
|
||||
void NCE::SetRegister(wreg reg_id, u32 value, pid_t pid) {
|
||||
(reinterpret_cast<u32 *>(®ister_map.at(pid ? pid : curr_pid).regs))[static_cast<uint>(reg_id) * 2] = value;
|
||||
}
|
||||
|
||||
u64 NCE::GetRegister(sreg reg_id, pid_t pid) {
|
||||
pid = pid ? pid : curr_pid;
|
||||
switch (reg_id) {
|
||||
case sreg::pc:
|
||||
return register_map.at(pid).pc;
|
||||
case sreg::sp:
|
||||
return register_map.at(pid).sp;
|
||||
case sreg::pstate:
|
||||
return register_map.at(pid).pstate;
|
||||
}
|
||||
}
|
||||
|
||||
void NCE::SetRegister(sreg reg_id, u32 value, pid_t pid) {
|
||||
pid = pid ? pid : curr_pid;
|
||||
switch (reg_id) {
|
||||
case sreg::pc:
|
||||
register_map.at(pid).pc = value;
|
||||
case sreg::sp:
|
||||
register_map.at(pid).sp = value;
|
||||
case sreg::pstate:
|
||||
register_map.at(pid).pstate = value;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<kernel::type::KSharedMemory> NCE::MapSharedRegion(const u64 address, const size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, const Memory::Type type, const Memory::Region region) {
|
||||
auto item = std::make_shared<kernel::type::KSharedMemory>(*state, size, local_permission, remote_permission, type, 0, 0);
|
||||
item->Map(address);
|
||||
memory_map[item->address] = item;
|
||||
memory_region_map[region] = item;
|
||||
return item;
|
||||
}
|
||||
|
||||
size_t NCE::GetSharedSize() {
|
||||
size_t shared_size = 0;
|
||||
for (auto ®ion : memory_map) {
|
||||
shared_size += region.second->size;
|
||||
}
|
||||
return shared_size;
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <syslog.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "common.h"
|
||||
#include "kernel/types/KSharedMemory.h"
|
||||
|
||||
namespace lightSwitch {
|
||||
class NCE {
|
||||
private:
|
||||
pid_t curr_pid = 0; //!< The PID of the process currently being handled, this is so the PID won't have to be passed into functions like ReadRegister redundantly
|
||||
std::unordered_map<pid_t, user_pt_regs> register_map; //!< A map of all PIDs and their corresponding registers (Whenever they were last updated)
|
||||
const device_state *state; //!< The state of the device
|
||||
|
||||
/**
|
||||
* Reads process registers into the `registers` variable
|
||||
* @param registers A set of registers to fill with values from the process
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void ReadRegisters(user_pt_regs ®isters, pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* Writes process registers from the `registers` variable
|
||||
* @param registers The registers to be written by the process
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void WriteRegisters(user_pt_regs ®isters, pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @param address The address of the BRK instruction
|
||||
* @param pid The PID of the process
|
||||
* @return An instance of BRK with the corresponding values
|
||||
*/
|
||||
instr::brk ReadBrk(u64 address, pid_t pid = 0) const;
|
||||
|
||||
public:
|
||||
std::map<Memory::Region, std::shared_ptr<kernel::type::KSharedMemory>> memory_region_map; //!< A mapping from every Memory::Region to a shared pointer to it's corresponding kernel::type::KSharedMemory
|
||||
std::map<u64, std::shared_ptr<kernel::type::KSharedMemory>> memory_map; //!< A mapping from every address to a shared pointer to it's corresponding kernel::type::KSharedMemory
|
||||
|
||||
/**
|
||||
* Initialize NCE by setting the device_state variable
|
||||
* @param state The state of the device
|
||||
*/
|
||||
void Initialize(const device_state &state);
|
||||
|
||||
/**
|
||||
* Start managing child processes
|
||||
*/
|
||||
void Execute();
|
||||
|
||||
/**
|
||||
* Execute any arbitrary function on a particular child process
|
||||
* @param func The entry point of the function
|
||||
* @param func_regs A set of registers to run the function with (PC, SP and X29 are replaced)
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void ExecuteFunction(void *func, user_pt_regs &func_regs, pid_t pid);
|
||||
|
||||
/**
|
||||
* Waits till a process calls "BRK #constant::brk_rdy"
|
||||
* @param pid The PID of the process
|
||||
* @return The registers after the BRK
|
||||
*/
|
||||
user_pt_regs WaitRdy(pid_t pid);
|
||||
|
||||
/**
|
||||
* Pauses a particular process if was not already paused
|
||||
* @param pid The PID of the process
|
||||
* @return If the application was paused beforehand
|
||||
*/
|
||||
bool PauseProcess(pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* Resumes a particular process, does nothing if it was already running
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void ResumeProcess(pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* Starts a particular process, sets the registers to their expected values and jumps to address
|
||||
* @param address The address to jump to
|
||||
* @param entry_arg The argument to pass in for the entry function
|
||||
* @param stack_top The top of the stack
|
||||
* @param handle The handle of the main thread (Set to value of 1st register)
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void StartProcess(u64 address, u64 entry_arg, u64 stack_top, u32 handle, pid_t pid) const;
|
||||
|
||||
/**
|
||||
* Get the value of a Xn register
|
||||
* @param reg_id The ID of the register
|
||||
* @param pid The PID of the process
|
||||
* @return The value of the register
|
||||
*/
|
||||
u64 GetRegister(xreg reg_id, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Set the value of a Xn register
|
||||
* @param reg_id The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void SetRegister(xreg reg_id, u64 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Get the value of a Wn register
|
||||
* @param reg_id The ID of the register
|
||||
* @param pid The PID of the process
|
||||
* @return The value in the register
|
||||
*/
|
||||
u64 GetRegister(wreg reg_id, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Set the value of a Wn register
|
||||
* @param reg_id The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void SetRegister(wreg reg_id, u32 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Get the value of a special register
|
||||
* @param reg_id The ID of the register
|
||||
* @param pid The PID of the process
|
||||
* @return The value in the register
|
||||
*/
|
||||
u64 GetRegister(sreg reg_id, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Set the value of a special register
|
||||
* @param reg_id The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void SetRegister(sreg reg_id, u32 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Map a chunk of shared memory
|
||||
* @param address The address to map to (Can be 0 if address doesn't matter)
|
||||
* @param size The size of the chunk of memory
|
||||
* @param perms The permissions of the memory
|
||||
* @param type The type of the memory
|
||||
* @param region The specific region this memory is mapped for
|
||||
* @return A shared pointer to the kernel::type::KSharedMemory object
|
||||
*/
|
||||
std::shared_ptr<kernel::type::KSharedMemory> MapSharedRegion(const u64 address, const size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, const Memory::Type type, const Memory::Region region);
|
||||
|
||||
/**
|
||||
* @return The total size of allocated shared memory
|
||||
*/
|
||||
size_t GetSharedSize();
|
||||
};
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
#include "os.h"
|
||||
#include "kernel/svc.h"
|
||||
#include "loader/nro.h"
|
||||
#include "nce.h"
|
||||
|
||||
extern bool halt;
|
||||
|
||||
namespace lightSwitch::kernel {
|
||||
OS::OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, this_process, this_thread, std::make_shared<NCE>(), settings, logger) {}
|
||||
|
||||
void OS::Execute(std::string rom_file) {
|
||||
state.nce->Initialize(state);
|
||||
std::string rom_ext = rom_file.substr(rom_file.find_last_of('.') + 1);
|
||||
std::transform(rom_ext.begin(), rom_ext.end(), rom_ext.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
if (rom_ext == "nro") loader::NroLoader loader(rom_file, state);
|
||||
else throw exception("Unsupported ROM extension.");
|
||||
|
||||
auto main_process = CreateProcess(state.nce->memory_region_map.at(Memory::Region::text)->address, constant::def_stack_size);
|
||||
main_process->thread_map[main_process->main_thread]->Start(); // The kernel itself is responsible for starting the main thread
|
||||
state.nce->Execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function executed by all child processes after cloning
|
||||
*/
|
||||
int ExecuteChild(void *) {
|
||||
ptrace(PTRACE_TRACEME);
|
||||
asm volatile("brk #0xFF"); // BRK #constant::brk_rdy (So we know when the thread/process is ready)
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<type::KProcess> OS::CreateProcess(u64 address, size_t stack_size) {
|
||||
auto *stack = static_cast<u8 *>(mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
if (stack == MAP_FAILED)
|
||||
throw exception("Failed to allocate stack memory");
|
||||
if (mprotect(stack, PAGE_SIZE, PROT_NONE)) {
|
||||
munmap(stack, stack_size);
|
||||
throw exception("Failed to create guard pages");
|
||||
}
|
||||
pid_t pid = clone(&ExecuteChild, stack + stack_size, CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
|
||||
if (pid == -1) throw exception(fmt::format("Call to clone() has failed: {}", strerror(errno)));
|
||||
std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(pid, address, reinterpret_cast<u64>(stack), stack_size, state);
|
||||
process_map[pid] = process;
|
||||
process_vec.push_back(pid);
|
||||
state.logger->Write(Logger::DEBUG, "Successfully created process with PID: {}", pid);
|
||||
return process;
|
||||
}
|
||||
|
||||
void OS::KillThread(pid_t pid) {
|
||||
auto process = process_map.at(pid);
|
||||
if (process->main_thread == pid) {
|
||||
state.logger->Write(Logger::DEBUG, "Exiting process with PID: {}", pid);
|
||||
// Erasing all shared_ptr instances to the process will call the destructor
|
||||
// However, in the case these are not all instances of it we wouldn't want to call the destructor
|
||||
for (auto&[key, value]: process->thread_map) {
|
||||
process_map.erase(key);
|
||||
};
|
||||
process_vec.erase(std::remove(process_vec.begin(), process_vec.end(), pid), process_vec.end());
|
||||
} else {
|
||||
state.logger->Write(Logger::DEBUG, "Exiting thread with TID: {}", pid);
|
||||
process->handle_table.erase(process->thread_map[pid]->handle);
|
||||
process->thread_map.erase(pid);
|
||||
process_map.erase(pid);
|
||||
}
|
||||
}
|
||||
|
||||
void OS::SvcHandler(u16 svc, pid_t pid) {
|
||||
this_process = process_map.at(pid);
|
||||
this_thread = this_process->thread_map.at(pid);
|
||||
if (svc::svcTable[svc]) {
|
||||
state.logger->Write(Logger::DEBUG, "SVC called 0x{:X}", svc);
|
||||
(*svc::svcTable[svc])(state);
|
||||
} else {
|
||||
throw exception(fmt::format("Unimplemented SVC 0x{:X}", svc));
|
||||
}
|
||||
}
|
||||
|
||||
ipc::IpcResponse OS::IpcHandler(handle_t handle) {
|
||||
ipc::IpcRequest request(false, state);
|
||||
ipc::IpcResponse response(false, state);
|
||||
switch (request.header->type) {
|
||||
case static_cast<u16>(ipc::CommandType::Request):
|
||||
case static_cast<u16>(ipc::CommandType::RequestWithContext):
|
||||
throw exception("Services are in-progress");
|
||||
default:
|
||||
throw exception(fmt::format("Unimplemented IPC message type {0}", u16(request.header->type)));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include "common.h"
|
||||
#include "kernel/ipc.h"
|
||||
#include "kernel/types/KProcess.h"
|
||||
#include "kernel/types/KThread.h"
|
||||
#include "nce.h"
|
||||
|
||||
namespace lightSwitch::kernel {
|
||||
/**
|
||||
* The OS class manages the interaction between OS components and the underlying hardware in NCE
|
||||
*/
|
||||
class OS {
|
||||
private:
|
||||
device_state state; //!< The state of the device
|
||||
|
||||
public:
|
||||
std::unordered_map<pid_t, std::shared_ptr<type::KProcess>> process_map; //!< A mapping from a process's PID to it's corresponding PID (Threads have their own PID too, so there are overlapping values)
|
||||
std::vector<pid_t> process_vec; //!< A vector of all processes by their main thread's PID
|
||||
std::shared_ptr<type::KProcess> this_process; //!< The corresponding KProcess object of the process that's called an SVC
|
||||
std::shared_ptr<type::KThread> this_thread; //!< The corresponding KThread object of the thread that's called an SVC
|
||||
|
||||
/**
|
||||
* @param logger An instance of the Logger class
|
||||
* @param settings An instance of the Settings class
|
||||
*/
|
||||
OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings);
|
||||
|
||||
/**
|
||||
* Execute a particular ROM file. This launches a the main processes and calls the NCE class to handle execution.
|
||||
* @param rom_file The path to the ROM file to execute
|
||||
*/
|
||||
void Execute(std::string rom_file);
|
||||
|
||||
/**
|
||||
* Creates a new process
|
||||
* @param address The address of the initial function
|
||||
* @param stack_size The size of the main stack
|
||||
* @return An instance of the KProcess of the created process
|
||||
*/
|
||||
std::shared_ptr<type::KProcess> CreateProcess(u64 address, size_t stack_size);
|
||||
|
||||
/**
|
||||
* Kill a particular thread
|
||||
* @param pid The PID of the thread
|
||||
*/
|
||||
void KillThread(pid_t pid);
|
||||
|
||||
/**
|
||||
* @param svc The ID of the SVC to be called
|
||||
* @param pid The PID of the process/thread calling the SVC
|
||||
*/
|
||||
void SvcHandler(u16 svc, pid_t pid);
|
||||
|
||||
/**
|
||||
* @param handle The handle of the object
|
||||
* @return The corresponding response returned by a service
|
||||
*/
|
||||
ipc::IpcResponse IpcHandler(handle_t handle);
|
||||
};
|
||||
}
|
@ -1,217 +0,0 @@
|
||||
package emu.lightswitch;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.FileObserver;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import static java.lang.Thread.interrupted;
|
||||
|
||||
public class LogActivity extends AppCompatActivity {
|
||||
File log_file;
|
||||
BufferedReader reader;
|
||||
Thread thread;
|
||||
LogAdapter adapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.log_activity);
|
||||
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null)
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
final ListView log_list = this.findViewById(R.id.log_list);
|
||||
adapter = new LogAdapter(this, Integer.parseInt(prefs.getString("log_level", "3")), getResources().getStringArray(R.array.log_level));
|
||||
log_list.setAdapter(adapter);
|
||||
log_file = new File(getApplicationInfo().dataDir + "/lightswitch.log");
|
||||
try {
|
||||
InputStream inputStream = new FileInputStream(log_file);
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@SuppressWarnings("deprecation") // Required as FileObserver(File) is only on API level 29 also no AndroidX version present
|
||||
FileObserver observer = new FileObserver(log_file.getPath()) {
|
||||
@Override
|
||||
public void onEvent(int event, String path) {
|
||||
if (event == FileObserver.MODIFY) {
|
||||
try {
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
final String line = reader.readLine();
|
||||
done = (line == null);
|
||||
if (!done) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.add(line);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
observer.onEvent(FileObserver.MODIFY, log_file.getPath());
|
||||
observer.startWatching();
|
||||
while (!interrupted()) ;
|
||||
observer.stopWatching();
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.file_missing), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_log, menu);
|
||||
MenuItem mSearch = menu.findItem(R.id.action_search_log);
|
||||
final SearchView searchView = (SearchView) mSearch.getActionView();
|
||||
searchView.setSubmitButtonEnabled(false);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchView.setIconified(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
adapter.getFilter().filter(newText);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_clear:
|
||||
try {
|
||||
FileWriter fileWriter = new FileWriter(log_file, false);
|
||||
fileWriter.close();
|
||||
} catch (IOException e) {
|
||||
Log.w("Logger", "IO Error while clearing the log file: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.cleared), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return true;
|
||||
case R.id.action_share_log:
|
||||
Thread share_thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
HttpsURLConnection urlConnection = null;
|
||||
try {
|
||||
URL url = new URL("https://hastebin.com/documents");
|
||||
urlConnection = (HttpsURLConnection) url.openConnection();
|
||||
urlConnection.setRequestMethod("POST");
|
||||
urlConnection.setRequestProperty("Host", "hastebin.com");
|
||||
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
|
||||
urlConnection.setRequestProperty("Referer", "https://hastebin.com/");
|
||||
urlConnection.setRequestProperty("Connection", "keep-alive");
|
||||
OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream());
|
||||
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
|
||||
FileReader fileReader = new FileReader(log_file);
|
||||
int chr;
|
||||
while ((chr = fileReader.read()) != -1) {
|
||||
bufferedWriter.write(chr);
|
||||
}
|
||||
bufferedWriter.flush();
|
||||
bufferedWriter.close();
|
||||
outputStream.close();
|
||||
if (urlConnection.getResponseCode() != 200) {
|
||||
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.getResponseCode());
|
||||
throw new Exception();
|
||||
}
|
||||
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||
String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key");
|
||||
bufferedReader.close();
|
||||
inputStream.close();
|
||||
String result = "https://hastebin.com/" + key;
|
||||
Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result);
|
||||
startActivity(Intent.createChooser(sharingIntent, "Share log url with:"));
|
||||
} catch (Exception e) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
assert urlConnection != null;
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
share_thread.start();
|
||||
try {
|
||||
share_thread.join(1000);
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
try {
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
Log.w("Logger", "IO Error during closing BufferedReader: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
} catch (NullPointerException ignored) {
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package emu.lightswitch;
|
||||
package emu.skyline;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@ -20,9 +20,9 @@ import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
class GameItem extends BaseItem {
|
||||
private File file;
|
||||
private final File file;
|
||||
transient private TitleEntry meta;
|
||||
private int index;
|
||||
private final int index;
|
||||
|
||||
GameItem(File file) {
|
||||
this.file = file;
|
||||
@ -49,7 +49,7 @@ class GameItem extends BaseItem {
|
||||
return meta.getAuthor();
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
private String getType() {
|
||||
return file.getName().substring(index + 1).toUpperCase();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package emu.lightswitch;
|
||||
package emu.skyline;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
@ -11,12 +11,18 @@ import android.widget.Filterable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch;
|
||||
import me.xdrop.fuzzywuzzy.model.ExtractedResult;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class ContentType implements Serializable {
|
||||
transient static final int Header = 0;
|
||||
transient static final int Item = 1;
|
||||
@ -178,9 +184,9 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
|
||||
}
|
||||
|
||||
class State<StateType> implements Serializable {
|
||||
private ArrayList<StateType> item_array;
|
||||
private ArrayList<String> header_array;
|
||||
private ArrayList<ContentType> type_array;
|
||||
private final ArrayList<StateType> item_array;
|
||||
private final ArrayList<String> header_array;
|
||||
private final ArrayList<ContentType> type_array;
|
||||
|
||||
State(ArrayList<StateType> item_array, ArrayList<String> header_array, ArrayList<ContentType> type_array) {
|
||||
this.item_array = item_array;
|
201
app/src/main/java/emu/skyline/LogActivity.java
Normal file
@ -0,0 +1,201 @@
|
||||
package emu.skyline;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.FileObserver;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import static java.lang.Thread.interrupted;
|
||||
|
||||
public class LogActivity extends AppCompatActivity {
|
||||
private File log_file;
|
||||
private BufferedReader reader;
|
||||
private Thread thread;
|
||||
private LogAdapter adapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.log_activity);
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null)
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
final ListView log_list = this.findViewById(R.id.log_list);
|
||||
adapter = new LogAdapter(this, Integer.parseInt(prefs.getString("log_level", "3")), getResources().getStringArray(R.array.log_level));
|
||||
log_list.setAdapter(adapter);
|
||||
log_file = new File(getApplicationInfo().dataDir + "/skyline.log");
|
||||
try {
|
||||
InputStream inputStream = new FileInputStream(log_file);
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
thread = new Thread(() -> {
|
||||
// Required as FileObserver(File) is only on API level 29 also no AndroidX version present
|
||||
FileObserver observer = new FileObserver(log_file.getPath()) {
|
||||
@Override
|
||||
public void onEvent(int event, String path) {
|
||||
if (event == FileObserver.MODIFY) {
|
||||
try {
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
final String line = reader.readLine();
|
||||
done = (line == null);
|
||||
if (!done) {
|
||||
runOnUiThread(() -> adapter.add(line));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
observer.onEvent(FileObserver.MODIFY, log_file.getPath());
|
||||
observer.startWatching();
|
||||
while (!interrupted()) ;
|
||||
observer.stopWatching();
|
||||
});
|
||||
thread.start();
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.file_missing), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_log, menu);
|
||||
MenuItem mSearch = menu.findItem(R.id.action_search_log);
|
||||
final SearchView searchView = (SearchView) mSearch.getActionView();
|
||||
searchView.setSubmitButtonEnabled(false);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchView.setIconified(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
adapter.getFilter().filter(newText);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_clear:
|
||||
try {
|
||||
FileWriter fileWriter = new FileWriter(log_file, false);
|
||||
fileWriter.close();
|
||||
} catch (IOException e) {
|
||||
Log.w("Logger", "IO Error while clearing the log file: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.cleared), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return true;
|
||||
case R.id.action_share_log:
|
||||
Thread share_thread = new Thread(() -> {
|
||||
HttpsURLConnection urlConnection = null;
|
||||
try {
|
||||
URL url = new URL("https://hastebin.com/documents");
|
||||
urlConnection = (HttpsURLConnection) url.openConnection();
|
||||
urlConnection.setRequestMethod("POST");
|
||||
urlConnection.setRequestProperty("Host", "hastebin.com");
|
||||
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
|
||||
urlConnection.setRequestProperty("Referer", "https://hastebin.com/");
|
||||
urlConnection.setRequestProperty("Connection", "keep-alive");
|
||||
OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream());
|
||||
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
|
||||
FileReader fileReader = new FileReader(log_file);
|
||||
int chr;
|
||||
while ((chr = fileReader.read()) != -1) {
|
||||
bufferedWriter.write(chr);
|
||||
}
|
||||
bufferedWriter.flush();
|
||||
bufferedWriter.close();
|
||||
outputStream.close();
|
||||
if (urlConnection.getResponseCode() != 200) {
|
||||
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.getResponseCode());
|
||||
throw new Exception();
|
||||
}
|
||||
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||
String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key");
|
||||
bufferedReader.close();
|
||||
inputStream.close();
|
||||
String result = "https://hastebin.com/" + key;
|
||||
Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result);
|
||||
startActivity(Intent.createChooser(sharingIntent, "Share log url with:"));
|
||||
} catch (Exception e) {
|
||||
runOnUiThread(() -> Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
assert urlConnection != null;
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
});
|
||||
share_thread.start();
|
||||
try {
|
||||
share_thread.join(1000);
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
try {
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
Log.w("Logger", "IO Error during closing BufferedReader: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
} catch (NullPointerException | InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package emu.lightswitch;
|
||||
package emu.skyline;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
@ -12,8 +12,8 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
class LogItem extends BaseItem {
|
||||
private String content;
|
||||
private String level;
|
||||
private final String content;
|
||||
private final String level;
|
||||
|
||||
LogItem(String content, String level) {
|
||||
this.content = content;
|
||||
@ -35,9 +35,9 @@ class LogItem extends BaseItem {
|
||||
}
|
||||
|
||||
public class LogAdapter extends HeaderAdapter<LogItem> implements View.OnLongClickListener {
|
||||
private ClipboardManager clipboard;
|
||||
private int debug_level;
|
||||
private String[] level_str;
|
||||
private final ClipboardManager clipboard;
|
||||
private final int debug_level;
|
||||
private final String[] level_str;
|
||||
|
||||
LogAdapter(Context context, int debug_level, String[] level_str) {
|
||||
super(context);
|
@ -1,4 +1,4 @@
|
||||
package emu.lightswitch;
|
||||
package emu.skyline;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
@ -9,13 +9,11 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
@ -32,11 +30,11 @@ import java.util.Objects;
|
||||
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
|
||||
static {
|
||||
System.loadLibrary("lightswitch");
|
||||
System.loadLibrary("skyline");
|
||||
}
|
||||
|
||||
SharedPreferences sharedPreferences;
|
||||
GameAdapter adapter;
|
||||
private SharedPreferences sharedPreferences = null;
|
||||
private GameAdapter adapter = null;
|
||||
|
||||
private void notifyUser(String text) {
|
||||
Snackbar.make(findViewById(android.R.id.content), text, Snackbar.LENGTH_SHORT).show();
|
||||
@ -103,20 +101,17 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
||||
setContentView(R.layout.main_activity);
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
FloatingActionButton log_fab = findViewById(R.id.log_fab);
|
||||
log_fab.setOnClickListener(this);
|
||||
adapter = new GameAdapter(this);
|
||||
ListView game_list = findViewById(R.id.game_list);
|
||||
game_list.setAdapter(adapter);
|
||||
game_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (adapter.getItemViewType(position) == ContentType.Item) {
|
||||
GameItem item = ((GameItem) parent.getItemAtPosition(position));
|
||||
notifyUser(getString(R.string.launching) + " " + item.getTitle());
|
||||
loadFile(item.getPath(), getApplicationInfo().dataDir + "/shared_prefs/" + getApplicationInfo().packageName + "_preferences.xml", getApplicationInfo().dataDir + "/lightswitch.log");
|
||||
}
|
||||
game_list.setOnItemClickListener((parent, view, position, id) -> {
|
||||
if (adapter.getItemViewType(position) == ContentType.Item) {
|
||||
GameItem item = ((GameItem) parent.getItemAtPosition(position));
|
||||
notifyUser(getString(R.string.launching) + " " + item.getTitle());
|
||||
loadFile(item.getPath(), getApplicationInfo().dataDir + "/shared_prefs/" + getApplicationInfo().packageName + "_preferences.xml", getApplicationInfo().dataDir + "/skyline.log");
|
||||
}
|
||||
});
|
||||
RefreshFiles(true);
|
@ -1,4 +1,4 @@
|
||||
package emu.lightswitch;
|
||||
package emu.skyline;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
@ -12,7 +12,7 @@ final class TitleEntry {
|
||||
private final String author;
|
||||
private final Bitmap icon;
|
||||
|
||||
public TitleEntry(String name, String author, Bitmap icon) {
|
||||
TitleEntry(String name, String author, Bitmap icon) {
|
||||
this.name = name;
|
||||
this.author = author;
|
||||
this.icon = icon;
|
||||
@ -31,8 +31,8 @@ final class TitleEntry {
|
||||
}
|
||||
}
|
||||
|
||||
public class NroLoader {
|
||||
public static TitleEntry getTitleEntry(String file) {
|
||||
class NroLoader {
|
||||
static TitleEntry getTitleEntry(String file) {
|
||||
try {
|
||||
RandomAccessFile f = new RandomAccessFile(file, "r");
|
||||
f.seek(0x18); // Skip to NroHeader.size
|
@ -1,10 +1,9 @@
|
||||
package emu.lightswitch;
|
||||
package emu.skyline;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity {
|
||||
@ -17,7 +16,7 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, new HeaderFragment())
|
||||
.commit();
|
||||
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
57
app/src/main/res/drawable/logo_skyline.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<vector android:height="200dp" android:viewportHeight="248"
|
||||
android:viewportWidth="248" android:width="200dp"
|
||||
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:pathData="M124,124m-124,0a124,124 0,1 1,248 0a124,124 0,1 1,-248 0">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:endX="124" android:endY="-9.094947E-13"
|
||||
android:startX="124" android:startY="248" android:type="linear">
|
||||
<item android:color="#FF9E005D" android:offset="0"/>
|
||||
<item android:color="#FF82045E" android:offset="0.1147"/>
|
||||
<item android:color="#FF5D0A60" android:offset="0.2951"/>
|
||||
<item android:color="#FF400E62" android:offset="0.4757"/>
|
||||
<item android:color="#FF2C1163" android:offset="0.6544"/>
|
||||
<item android:color="#FF1F1364" android:offset="0.8303"/>
|
||||
<item android:color="#FF1B1464" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M88.9,61.9l0.8,1.5c0.2,0.4 0.5,0.7 0.9,0.9l1.5,0.8c1.5,0.8 1.5,3 0,3.9l-1.5,0.8c-0.4,0.2 -0.7,0.5 -0.9,0.9l-0.8,1.5c-0.8,1.5 -3,1.5 -3.9,0l-0.8,-1.5c-0.2,-0.4 -0.5,-0.7 -0.9,-0.9l-1.5,-0.8c-1.5,-0.8 -1.5,-3 0,-3.9l1.5,-0.8c0.4,-0.2 0.7,-0.5 0.9,-0.9l0.8,-1.5C85.9,60.4 88.1,60.4 88.9,61.9z"/>
|
||||
<path android:pathData="M41.7,78.1v84.4h0c-15.5,0 -28.1,-12.6 -28.1,-28.1v-28.1C13.6,90.7 26.2,78.1 41.7,78.1L41.7,78.1z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:endX="59.927605" android:endY="144.2635"
|
||||
android:startX="11.915204" android:startY="96.2512" android:type="linear">
|
||||
<item android:color="#FF9E005D" android:offset="0"/>
|
||||
<item android:color="#00FFFFFF" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M206.3,86.6v84.4h0c15.5,0 28.1,-12.6 28.1,-28.1v-28.1C234.4,99.1 221.8,86.6 206.3,86.6L206.3,86.6z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:endX="236.0847" android:endY="104.7364"
|
||||
android:startX="188.07231" android:startY="152.7488" android:type="linear">
|
||||
<item android:color="#FF23F6FF" android:offset="0"/>
|
||||
<item android:color="#00FFFFFF" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M49.2,95.6v68.2c0,4 3.2,7.2 7.2,7.2h135.7c4,0 7.2,-3.2 7.2,-7.2V95.6c0,-4 -3.2,-7.2 -7.2,-7.2H56.4C52.5,88.4 49.2,91.6 49.2,95.6z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:endX="180.2715" android:endY="185.7053"
|
||||
android:startX="68.22111" android:startY="73.6549" android:type="linear">
|
||||
<item android:color="#FF9E005D" android:offset="0"/>
|
||||
<item android:color="#00FFFFFF" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M95,59c44.7,-31.9 88.7,-38 107,-20c3.7,3.7 8.7,10.6 10,24"
|
||||
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="5">
|
||||
<aapt:attr name="android:strokeColor">
|
||||
<gradient android:endX="214.5" android:endY="46.0098"
|
||||
android:startX="92.5" android:startY="46.0098" android:type="linear">
|
||||
<item android:color="#FFFFFFFF" android:offset="0"/>
|
||||
<item android:color="#00FFFFFF" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 19 KiB |
@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">LightSwitch</string>
|
||||
<string name="app_name">Skyline</string>
|
||||
<!-- Common -->
|
||||
<string name="search">Search</string>
|
||||
<!-- Toolbar Main -->
|
||||
|
4
app/src/main/res/xml/backup_descriptor.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<include domain="sharedpref" path="."/>
|
||||
</full-backup-content>
|