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.
This commit is contained in:
◱ PixelyIon 2019-09-25 02:24:27 +05:30
parent a54f5ff578
commit 7ad2e11705
95 changed files with 4121 additions and 2424 deletions

View File

@ -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">

View File

@ -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>

View File

@ -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">

View File

@ -0,0 +1,3 @@
<component name="DependencyValidationManager">
<scope name="SkylineCPP" pattern="file[app]:src/main/cpp//*&amp;&amp;!file[Lightswitch]:*&amp;&amp;!file[app]:*" />
</component>

View File

@ -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)

View File

@ -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'
}

View File

@ -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" />

View File

@ -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
View 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);
}

View 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();
}
}

View 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
};
}

View 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);
}
}

View 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();
};
}

View 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
}
}

View 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);
};
}

View 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]);
}
}

View 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);
};
}

View 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() {};
};
}

View 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);
}
}

View 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);
};
}

View 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);
}
}

View 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);
};
}

View 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);
}
}
}
}

View 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);
};
}

View 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);
}
}

View File

@ -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

View 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) {}
};
}

View 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;
}
};

View 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();
};
}

View 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 &region : 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;
}
}

View 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));
}
}
};
}

View 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++;
}
};
}

View File

@ -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;
}
};

View 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);
};
}

View 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);
}
}
}
}
}
}

View 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();
};
}

View 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));
}
}

View 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);
};
}

View File

@ -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) {}
};
}

View 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);
}
}
}
}

View File

@ -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);
};
}

View File

@ -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
};
}

View 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 &registers, pid_t pid) const {
iovec iov = {&registers, 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 &registers, pid_t pid) const {
iovec iov = {&registers, 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 *>(&registerMap.at(pid ? pid : currPid).regs))[static_cast<uint>(regId) * 2];
}
void NCE::SetRegister(Wreg regId, u32 value, pid_t pid) {
(reinterpret_cast<u32 *>(&registerMap.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 &region : memoryRegionMap)
sharedSize += region.second->size;
return sharedSize;
}
}

View 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 &registers, 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 &registers, 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();
};
}

View 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));
}
}

View 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);
};
}

View File

@ -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();
}
}

View File

@ -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;
};
}

View File

@ -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);
}
}
}
}

View File

@ -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();
};
}

View File

@ -1,8 +0,0 @@
#include "service.h"
namespace lightSwitch::kernel::service {
Service::Service() {
}
}

View File

@ -1,9 +0,0 @@
#pragma once
#include "../nce.h"
namespace lightSwitch::kernel::service {
class Service {
Service();
};
}

View File

@ -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;
};
}

View File

@ -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);
}
}

View File

@ -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) {}
};
}

View File

@ -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;
}
};

View File

@ -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();
};
}

View File

@ -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 &region : 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
}
}

View File

@ -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);
};
}

View File

@ -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);
};
}

View File

@ -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));
}
}

View File

@ -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);
};
}

View File

@ -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);
}
}
}
}

View File

@ -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 &registers, pid_t pid) const {
iovec iov = {&registers, 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 &registers, pid_t pid) const {
iovec iov = {&registers, 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 *>(&register_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 *>(&register_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 &region : memory_map) {
shared_size += region.second->size;
}
return shared_size;
}
}

View File

@ -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 &registers, 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 &registers, 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();
};
}

View File

@ -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;
}
}

View File

@ -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);
};
}

View File

@ -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) {
}
}
}

View File

@ -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();
}

View File

@ -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;

View 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) {
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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);

View 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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -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 -->

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<include domain="sharedpref" path="."/>
</full-backup-content>