2018-11-10 11:16:13 +00:00
/*
2019-04-08 03:00:49 +01:00
* Copyright ( c ) 2018 - 2019 Atmosphère - NX
2018-11-10 11:16:13 +00:00
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <switch.h>
2018-11-13 21:11:41 +00:00
# include <atmosphere/version.h>
2018-11-10 11:16:13 +00:00
# include "fatal_task_screen.hpp"
2018-11-10 20:38:24 +00:00
# include "fatal_config.hpp"
2018-11-13 14:03:30 +00:00
# include "fatal_font.hpp"
# include "ams_logo.hpp"
static constexpr u32 FatalScreenWidth = 1280 ;
static constexpr u32 FatalScreenHeight = 720 ;
static constexpr u32 FatalScreenBpp = 2 ;
static constexpr u32 FatalScreenWidthAlignedBytes = ( FatalScreenWidth * FatalScreenBpp + 63 ) & ~ 63 ;
static constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp ;
u32 GetPixelOffset ( uint32_t x , uint32_t y )
{
u32 tmp_pos ;
tmp_pos = ( ( y & 127 ) / 16 ) + ( x / 32 * 8 ) + ( ( y / 16 / 8 ) * ( ( ( FatalScreenWidthAligned / 2 ) / 16 * 8 ) ) ) ;
tmp_pos * = 16 * 16 * 4 ;
tmp_pos + = ( ( y % 16 ) / 8 ) * 512 + ( ( x % 32 ) / 16 ) * 256 + ( ( y % 8 ) / 2 ) * 64 + ( ( x % 16 ) / 8 ) * 32 + ( y % 2 ) * 16 + ( x % 8 ) * 2 ; //This line is a modified version of code from the Tegra X1 datasheet.
return tmp_pos / 2 ;
}
2018-11-10 11:16:13 +00:00
2018-11-13 06:26:13 +00:00
Result ShowFatalTask : : SetupDisplayInternal ( ) {
Result rc ;
ViDisplay display ;
/* Try to open the display. */
if ( R_FAILED ( ( rc = viOpenDisplay ( " Internal " , & display ) ) ) ) {
2019-03-29 03:39:17 +00:00
if ( rc = = ResultViNotFound ) {
2019-03-29 05:39:39 +00:00
return ResultSuccess ;
2018-11-13 06:26:13 +00:00
} else {
return rc ;
}
}
/* Guarantee we close the display. */
ON_SCOPE_EXIT { viCloseDisplay ( & display ) ; } ;
/* Turn on the screen. */
if ( R_FAILED ( ( rc = viSetDisplayPowerState ( & display , ViPowerState_On ) ) ) ) {
return rc ;
}
/* Set alpha to 1.0f. */
if ( R_FAILED ( ( rc = viSetDisplayAlpha ( & display , 1.0f ) ) ) ) {
return rc ;
}
return rc ;
}
Result ShowFatalTask : : SetupDisplayExternal ( ) {
Result rc ;
ViDisplay display ;
/* Try to open the display. */
if ( R_FAILED ( ( rc = viOpenDisplay ( " External " , & display ) ) ) ) {
2019-03-29 03:39:17 +00:00
if ( rc = = ResultViNotFound ) {
2019-03-29 05:39:39 +00:00
return ResultSuccess ;
2018-11-13 06:26:13 +00:00
} else {
return rc ;
}
}
/* Guarantee we close the display. */
ON_SCOPE_EXIT { viCloseDisplay ( & display ) ; } ;
/* Set alpha to 1.0f. */
if ( R_FAILED ( ( rc = viSetDisplayAlpha ( & display , 1.0f ) ) ) ) {
return rc ;
}
return rc ;
}
Result ShowFatalTask : : PrepareScreenForDrawing ( ) {
2019-03-29 05:39:39 +00:00
Result rc = ResultSuccess ;
2018-11-13 06:26:13 +00:00
/* Connect to vi. */
if ( R_FAILED ( ( rc = viInitialize ( ViServiceType_Manager ) ) ) ) {
return rc ;
}
/* Close other content. */
viSetContentVisibility ( false ) ;
/* Setup the two displays. */
if ( R_FAILED ( ( rc = SetupDisplayInternal ( ) ) ) | | R_FAILED ( ( rc = SetupDisplayExternal ( ) ) ) ) {
return rc ;
}
/* Open the default display. */
if ( R_FAILED ( ( rc = viOpenDefaultDisplay ( & this - > display ) ) ) ) {
return rc ;
}
/* Reset the display magnification to its default value. */
u32 display_width , display_height ;
if ( R_FAILED ( ( rc = viGetDisplayLogicalResolution ( & this - > display , & display_width , & display_height ) ) ) ) {
return rc ;
}
2019-03-06 22:39:48 +00:00
/* viSetDisplayMagnification was added in 3.0.0. */
if ( GetRuntimeFirmwareVersion ( ) > = FirmwareVersion_300 ) {
if ( R_FAILED ( ( rc = viSetDisplayMagnification ( & this - > display , 0 , 0 , display_width , display_height ) ) ) ) {
return rc ;
}
2018-11-13 06:26:13 +00:00
}
/* Create layer to draw to. */
if ( R_FAILED ( ( rc = viCreateLayer ( & this - > display , & this - > layer ) ) ) ) {
return rc ;
}
/* Setup the layer. */
{
/* Display a layer of 1280 x 720 at 1.5x magnification */
/* NOTE: N uses 2 (770x400) RGBA4444 buffers (tiled buffer + linear). */
/* We use a single 1280x720 tiled RGB565 buffer. */
2018-11-13 14:03:30 +00:00
constexpr u32 raw_width = FatalScreenWidth ;
constexpr u32 raw_height = FatalScreenHeight ;
2018-11-13 06:26:13 +00:00
constexpr u32 layer_width = ( ( raw_width ) * 3 ) / 2 ;
constexpr u32 layer_height = ( ( raw_height ) * 3 ) / 2 ;
const float layer_x = static_cast < float > ( ( display_width - layer_width ) / 2 ) ;
const float layer_y = static_cast < float > ( ( display_height - layer_height ) / 2 ) ;
u64 layer_z ;
if ( R_FAILED ( ( rc = viSetLayerSize ( & this - > layer , layer_width , layer_height ) ) ) ) {
return rc ;
}
/* Set the layer's Z at display maximum, to be above everything else .*/
/* NOTE: Fatal hardcodes 100 here. */
if ( R_SUCCEEDED ( ( rc = viGetDisplayMaximumZ ( & this - > display , & layer_z ) ) ) ) {
if ( R_FAILED ( ( rc = viSetLayerZ ( & this - > layer , layer_z ) ) ) ) {
return rc ;
}
}
/* Center the layer in the screen. */
if ( R_FAILED ( ( rc = viSetLayerPosition ( & this - > layer , layer_x , layer_y ) ) ) ) {
return rc ;
}
/* Create framebuffer. */
if ( R_FAILED ( rc = nwindowCreateFromLayer ( & this - > win , & this - > layer ) ) ) {
return rc ;
}
if ( R_FAILED ( rc = framebufferCreate ( & this - > fb , & this - > win , raw_width , raw_height , PIXEL_FORMAT_RGB_565 , 1 ) ) ) {
return rc ;
}
}
return rc ;
}
2018-11-10 19:59:55 +00:00
2018-11-13 06:26:13 +00:00
Result ShowFatalTask : : ShowFatal ( ) {
2019-03-29 05:39:39 +00:00
Result rc = ResultSuccess ;
2018-11-14 01:53:26 +00:00
const FatalConfig * config = GetFatalConfig ( ) ;
2018-11-10 19:59:55 +00:00
2018-11-13 06:26:13 +00:00
if ( R_FAILED ( ( rc = PrepareScreenForDrawing ( ) ) ) ) {
* ( volatile u32 * ) ( 0xCAFEBABE ) = rc ;
return rc ;
}
/* Dequeue a buffer. */
u16 * tiled_buf = reinterpret_cast < u16 * > ( framebufferBegin ( & this - > fb , NULL ) ) ;
if ( tiled_buf = = nullptr ) {
2019-03-28 22:37:50 +00:00
return ResultFatalNullGraphicsBuffer ;
2018-11-13 06:26:13 +00:00
}
2018-11-13 14:03:30 +00:00
/* Let the font manager know about our framebuffer. */
FontManager : : ConfigureFontFramebuffer ( tiled_buf , GetPixelOffset ) ;
FontManager : : SetFontColor ( 0xFFFF ) ;
2018-11-13 06:26:13 +00:00
/* Draw a background. */
for ( size_t i = 0 ; i < this - > fb . fb_size / sizeof ( * tiled_buf ) ; i + + ) {
tiled_buf [ i ] = 0x39C9 ;
}
2018-11-13 14:03:30 +00:00
/* Draw the atmosphere logo in the bottom right corner. */
for ( size_t y = 0 ; y < AMS_LOGO_HEIGHT ; y + + ) {
for ( size_t x = 0 ; x < AMS_LOGO_WIDTH ; x + + ) {
2018-11-14 01:53:26 +00:00
tiled_buf [ GetPixelOffset ( FatalScreenWidth - AMS_LOGO_WIDTH - 32 + x , 32 + y ) ] = AMS_LOGO_BIN [ y * AMS_LOGO_WIDTH + x ] ;
2018-11-13 14:03:30 +00:00
}
}
2018-11-13 06:26:13 +00:00
2018-11-13 14:03:30 +00:00
/* TODO: Actually draw meaningful shit here. */
2018-11-13 21:11:41 +00:00
FontManager : : SetPosition ( 32 , 64 ) ;
2018-11-14 01:53:26 +00:00
FontManager : : SetFontSize ( 16.0f ) ;
2018-11-14 02:07:27 +00:00
FontManager : : PrintFormat ( config - > error_msg , R_MODULE ( this - > ctx - > error_code ) , R_DESCRIPTION ( this - > ctx - > error_code ) , this - > ctx - > error_code ) ;
2018-11-13 21:28:05 +00:00
FontManager : : AddSpacingLines ( 0.5f ) ;
2018-11-14 03:30:40 +00:00
FontManager : : PrintFormatLine ( " Title: %016lX " , this - > title_id ) ;
2018-11-14 01:53:26 +00:00
FontManager : : AddSpacingLines ( 0.5f ) ;
FontManager : : PrintFormatLine ( u8 " Firmware: %s (Atmosphère %u.%u.%u-%s) " , GetFatalConfig ( ) - > firmware_version . display_version , CURRENT_ATMOSPHERE_VERSION , GetAtmosphereGitRevision ( ) ) ;
2018-11-14 02:07:27 +00:00
FontManager : : AddSpacingLines ( 1.5f ) ;
2019-03-29 03:19:38 +00:00
if ( this - > ctx - > error_code ! = ResultAtmosphereVersionMismatch ) {
2018-11-30 12:51:27 +00:00
FontManager : : Print ( config - > error_desc ) ;
} else {
/* Print a special message for atmosphere version mismatch. */
FontManager : : Print ( u8 " Atmosphère version mismatch detected. \n \n "
2019-01-24 16:20:27 +00:00
u8 " Please press the POWER Button to restart the console normally, or a VOL button \n "
u8 " to reboot to a payload (or RCM, if none is present). If you are unable to \n "
u8 " restart the console, hold the POWER Button for 12 seconds to turn the console off. \n \n "
2018-11-30 12:51:27 +00:00
u8 " Please ensure that all Atmosphère components are updated. \n "
u8 " github.com/Atmosphere-NX/Atmosphere/releases \n " ) ;
}
2018-11-14 01:53:26 +00:00
/* Add a line. */
for ( size_t x = 32 ; x < FatalScreenWidth - 32 ; x + + ) {
tiled_buf [ GetPixelOffset ( x , FontManager : : GetY ( ) ) ] = 0xFFFF ;
}
FontManager : : AddSpacingLines ( 1.5f ) ;
u32 backtrace_y = FontManager : : GetY ( ) ;
u32 backtrace_x = 0 ;
/* Print GPRs. */
FontManager : : SetFontSize ( 14.0f ) ;
2018-11-14 11:23:28 +00:00
FontManager : : Print ( " General Purpose Registers " ) ;
{
FontManager : : SetPosition ( FontManager : : GetX ( ) + 2 , FontManager : : GetY ( ) ) ;
u32 x = FontManager : : GetX ( ) ;
FontManager : : Print ( " PC: " ) ;
FontManager : : SetPosition ( x + 47 , FontManager : : GetY ( ) ) ;
}
if ( this - > ctx - > cpu_ctx . is_aarch32 ) {
FontManager : : PrintMonospaceU32 ( this - > ctx - > cpu_ctx . aarch32_ctx . pc ) ;
} else {
FontManager : : PrintMonospaceU64 ( this - > ctx - > cpu_ctx . aarch64_ctx . pc ) ;
}
FontManager : : PrintLine ( " " ) ;
FontManager : : SetPosition ( 32 , FontManager : : GetY ( ) ) ;
2018-11-14 01:53:26 +00:00
FontManager : : AddSpacingLines ( 0.5f ) ;
if ( this - > ctx - > cpu_ctx . is_aarch32 ) {
for ( size_t i = 0 ; i < ( NumAarch32Gprs / 2 ) ; i + + ) {
u32 x = FontManager : : GetX ( ) ;
FontManager : : PrintFormat ( " %s: " , Aarch32GprNames [ i ] ) ;
FontManager : : SetPosition ( x + 47 , FontManager : : GetY ( ) ) ;
2018-11-14 11:23:28 +00:00
if ( this - > ctx - > has_gprs [ i ] ) {
FontManager : : PrintMonospaceU32 ( this - > ctx - > cpu_ctx . aarch32_ctx . r [ i ] ) ;
} else {
FontManager : : PrintMonospaceBlank ( 8 ) ;
}
2018-11-14 03:30:40 +00:00
FontManager : : Print ( " " ) ;
2018-11-14 01:53:26 +00:00
x = FontManager : : GetX ( ) ;
FontManager : : PrintFormat ( " %s: " , Aarch32GprNames [ i + ( NumAarch32Gprs / 2 ) ] ) ;
FontManager : : SetPosition ( x + 47 , FontManager : : GetY ( ) ) ;
2018-11-14 11:23:28 +00:00
if ( this - > ctx - > has_gprs [ i + ( NumAarch32Gprs / 2 ) ] ) {
FontManager : : PrintMonospaceU32 ( this - > ctx - > cpu_ctx . aarch32_ctx . r [ i + ( NumAarch32Gprs / 2 ) ] ) ;
} else {
FontManager : : PrintMonospaceBlank ( 8 ) ;
}
2018-11-14 01:53:26 +00:00
2018-11-14 03:30:40 +00:00
if ( i = = ( NumAarch32Gprs / 2 ) - 1 ) {
FontManager : : Print ( " " ) ;
backtrace_x = FontManager : : GetX ( ) ;
}
2018-11-14 01:53:26 +00:00
FontManager : : PrintLine ( " " ) ;
FontManager : : SetPosition ( 32 , FontManager : : GetY ( ) ) ;
}
} else {
for ( size_t i = 0 ; i < NumAarch64Gprs / 2 ; i + + ) {
u32 x = FontManager : : GetX ( ) ;
FontManager : : PrintFormat ( " %s: " , Aarch64GprNames [ i ] ) ;
FontManager : : SetPosition ( x + 47 , FontManager : : GetY ( ) ) ;
2018-11-14 11:23:28 +00:00
if ( this - > ctx - > has_gprs [ i ] ) {
FontManager : : PrintMonospaceU64 ( this - > ctx - > cpu_ctx . aarch64_ctx . x [ i ] ) ;
} else {
FontManager : : PrintMonospaceBlank ( 16 ) ;
}
2018-11-14 03:30:40 +00:00
FontManager : : Print ( " " ) ;
2018-11-14 01:53:26 +00:00
x = FontManager : : GetX ( ) ;
FontManager : : PrintFormat ( " %s: " , Aarch64GprNames [ i + ( NumAarch64Gprs / 2 ) ] ) ;
FontManager : : SetPosition ( x + 47 , FontManager : : GetY ( ) ) ;
2018-11-14 11:23:28 +00:00
if ( this - > ctx - > has_gprs [ i + ( NumAarch64Gprs / 2 ) ] ) {
FontManager : : PrintMonospaceU64 ( this - > ctx - > cpu_ctx . aarch64_ctx . x [ i + ( NumAarch64Gprs / 2 ) ] ) ;
} else {
FontManager : : PrintMonospaceBlank ( 16 ) ;
}
2018-11-14 01:53:26 +00:00
if ( i = = ( NumAarch64Gprs / 2 ) - 1 ) {
FontManager : : Print ( " " ) ;
backtrace_x = FontManager : : GetX ( ) ;
}
FontManager : : PrintLine ( " " ) ;
FontManager : : SetPosition ( 32 , FontManager : : GetY ( ) ) ;
}
}
2018-11-13 21:11:41 +00:00
2018-11-14 01:53:26 +00:00
/* Print Backtrace. */
2018-11-14 02:11:08 +00:00
u32 bt_size ;
2018-11-14 01:53:26 +00:00
if ( this - > ctx - > cpu_ctx . is_aarch32 ) {
2018-11-14 02:11:08 +00:00
bt_size = this - > ctx - > cpu_ctx . aarch32_ctx . stack_trace_size ;
} else {
bt_size = this - > ctx - > cpu_ctx . aarch64_ctx . stack_trace_size ;
}
2018-11-14 03:30:40 +00:00
FontManager : : SetPosition ( backtrace_x , backtrace_y ) ;
2018-11-14 02:11:08 +00:00
if ( bt_size = = 0 ) {
if ( this - > ctx - > cpu_ctx . is_aarch32 ) {
2018-11-14 03:33:21 +00:00
FontManager : : Print ( " Start Address: " ) ;
FontManager : : PrintMonospaceU32 ( this - > ctx - > cpu_ctx . aarch32_ctx . start_address ) ;
FontManager : : PrintLine ( " " ) ;
2018-11-14 02:11:08 +00:00
} else {
2018-11-14 03:33:21 +00:00
FontManager : : Print ( " Start Address: " ) ;
FontManager : : PrintMonospaceU64 ( this - > ctx - > cpu_ctx . aarch64_ctx . start_address ) ;
FontManager : : PrintLine ( " " ) ;
2018-11-14 01:53:26 +00:00
}
2018-11-14 02:11:08 +00:00
} else {
if ( this - > ctx - > cpu_ctx . is_aarch32 ) {
2018-11-14 04:22:54 +00:00
FontManager : : Print ( " Backtrace - Start Address: " ) ;
2018-11-14 03:33:21 +00:00
FontManager : : PrintMonospaceU32 ( this - > ctx - > cpu_ctx . aarch32_ctx . start_address ) ;
FontManager : : PrintLine ( " " ) ;
2018-11-14 02:11:08 +00:00
FontManager : : AddSpacingLines ( 0.5f ) ;
for ( u32 i = 0 ; i < Aarch32CpuContext : : MaxStackTraceDepth / 2 ; i + + ) {
u32 bt_cur = 0 , bt_next = 0 ;
if ( i < this - > ctx - > cpu_ctx . aarch32_ctx . stack_trace_size ) {
bt_cur = this - > ctx - > cpu_ctx . aarch32_ctx . stack_trace [ i ] ;
}
if ( i + Aarch32CpuContext : : MaxStackTraceDepth / 2 < this - > ctx - > cpu_ctx . aarch32_ctx . stack_trace_size ) {
bt_next = this - > ctx - > cpu_ctx . aarch32_ctx . stack_trace [ i + Aarch32CpuContext : : MaxStackTraceDepth / 2 ] ;
}
if ( i < this - > ctx - > cpu_ctx . aarch32_ctx . stack_trace_size ) {
u32 x = FontManager : : GetX ( ) ;
2018-11-14 03:30:40 +00:00
FontManager : : PrintFormat ( " BT[%02d]: " , i ) ;
FontManager : : SetPosition ( x + 72 , FontManager : : GetY ( ) ) ;
FontManager : : PrintMonospaceU32 ( bt_cur ) ;
FontManager : : Print ( " " ) ;
2018-11-14 02:11:08 +00:00
}
if ( i + Aarch32CpuContext : : MaxStackTraceDepth / 2 < this - > ctx - > cpu_ctx . aarch32_ctx . stack_trace_size ) {
u32 x = FontManager : : GetX ( ) ;
2018-11-14 03:30:40 +00:00
FontManager : : PrintFormat ( " BT[%02d]: " , i + Aarch32CpuContext : : MaxStackTraceDepth / 2 ) ;
FontManager : : SetPosition ( x + 72 , FontManager : : GetY ( ) ) ;
FontManager : : PrintMonospaceU32 ( bt_next ) ;
2018-11-14 02:11:08 +00:00
}
FontManager : : PrintLine ( " " ) ;
FontManager : : SetPosition ( backtrace_x , FontManager : : GetY ( ) ) ;
2018-11-14 01:53:26 +00:00
}
2018-11-14 03:30:40 +00:00
} else {
2018-11-14 04:22:54 +00:00
FontManager : : Print ( " Backtrace - Start Address: " ) ;
2018-11-14 03:33:21 +00:00
FontManager : : PrintMonospaceU64 ( this - > ctx - > cpu_ctx . aarch64_ctx . start_address ) ;
FontManager : : PrintLine ( " " ) ;
2018-11-14 04:22:54 +00:00
FontManager : : AddSpacingLines ( 0.5f ) ;
2018-11-14 02:11:08 +00:00
for ( u32 i = 0 ; i < Aarch64CpuContext : : MaxStackTraceDepth / 2 ; i + + ) {
u64 bt_cur = 0 , bt_next = 0 ;
if ( i < this - > ctx - > cpu_ctx . aarch64_ctx . stack_trace_size ) {
bt_cur = this - > ctx - > cpu_ctx . aarch64_ctx . stack_trace [ i ] ;
}
if ( i + Aarch64CpuContext : : MaxStackTraceDepth / 2 < this - > ctx - > cpu_ctx . aarch64_ctx . stack_trace_size ) {
bt_next = this - > ctx - > cpu_ctx . aarch64_ctx . stack_trace [ i + Aarch64CpuContext : : MaxStackTraceDepth / 2 ] ;
}
if ( i < this - > ctx - > cpu_ctx . aarch64_ctx . stack_trace_size ) {
u32 x = FontManager : : GetX ( ) ;
2018-11-14 03:30:40 +00:00
FontManager : : PrintFormat ( " BT[%02d]: " , i ) ;
FontManager : : SetPosition ( x + 72 , FontManager : : GetY ( ) ) ;
FontManager : : PrintMonospaceU64 ( bt_cur ) ;
FontManager : : Print ( " " ) ;
2018-11-14 02:11:08 +00:00
}
if ( i + Aarch64CpuContext : : MaxStackTraceDepth / 2 < this - > ctx - > cpu_ctx . aarch64_ctx . stack_trace_size ) {
u32 x = FontManager : : GetX ( ) ;
2018-11-14 03:30:40 +00:00
FontManager : : PrintFormat ( " BT[%02d]: " , i + Aarch64CpuContext : : MaxStackTraceDepth / 2 ) ;
FontManager : : SetPosition ( x + 72 , FontManager : : GetY ( ) ) ;
FontManager : : PrintMonospaceU64 ( bt_next ) ;
2018-11-14 02:11:08 +00:00
}
FontManager : : PrintLine ( " " ) ;
FontManager : : SetPosition ( backtrace_x , FontManager : : GetY ( ) ) ;
2018-11-14 01:53:26 +00:00
}
}
2018-11-14 02:11:08 +00:00
}
2018-11-13 22:32:50 +00:00
2018-11-13 06:26:13 +00:00
/* Enqueue the buffer. */
framebufferEnd ( & fb ) ;
2018-11-10 19:59:55 +00:00
return rc ;
}
Result ShowFatalTask : : Run ( ) {
/* Don't show the fatal error screen until we've verified the battery is okay. */
eventWait ( this - > battery_event , U64_MAX ) ;
return ShowFatal ( ) ;
}
2018-11-10 11:16:13 +00:00
void BacklightControlTask : : TurnOnBacklight ( ) {
lblSwitchBacklightOn ( 0 ) ;
}
Result BacklightControlTask : : Run ( ) {
TurnOnBacklight ( ) ;
2019-03-29 05:39:39 +00:00
return ResultSuccess ;
2018-11-10 11:16:13 +00:00
}