forked from chinosk/gkms-local
				
			Compare commits
	
		
			45 Commits
		
	
	
		
			feature-xb
			...
			main
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						b70376dcef | |
| 
							
							
								
								 | 
						e859589125 | |
| 
							
							
								
								 | 
						d09904643f | |
| 
							
							
								
								 | 
						d83854c755 | |
| 
							
							
								
								 | 
						ade47131f9 | |
| 
							
							
								
								 | 
						0218543935 | |
| 
							
							
								
								 | 
						60c846b2f0 | |
| 
							
							
								 | 
						01591e61c0 | |
| 
							
							
								
								 | 
						56c066bf42 | |
| 
							
							
								
								 | 
						35c2b9f489 | |
| 
							
							
								
								 | 
						c50fdfd678 | |
| 
							
							
								
								 | 
						3c1d1f139a | |
| 
							
							
								
								 | 
						6ac94178fa | |
| 
							
							
								
								 | 
						c27085772f | |
| 
							
							
								
								 | 
						361c48e2c9 | |
| 
							
							
								
								 | 
						e9ba8b58fd | |
| 
							
							
								
								 | 
						e03736bd7d | |
| 
							
							
								
								 | 
						06b552a097 | |
| 
							
							
								
								 | 
						bd9bcae01d | |
| 
							
							
								
								 | 
						8c850ad7db | |
| 
							
							
								
								 | 
						7bf429336b | |
| 
							
							
								
								 | 
						c7e3d4f718 | |
| 
							
							
								
								 | 
						b74713be78 | |
| 
							
							
								
								 | 
						67945c86dd | |
| 
							
							
								
								 | 
						06a96a450e | |
| 
							
							
								 | 
						6e512d9380 | |
| 
							
							
								
								 | 
						f82e73845a | |
| 
							
							
								
								 | 
						8ddd6f53bc | |
| 
							
							
								
								 | 
						24480b6982 | |
| 
							
							
								 | 
						adb1a687b8 | |
| 
							
							
								 | 
						0e1ad6959b | |
| 
							
							
								 | 
						6ddf4212d4 | |
| 
							
							
								
								 | 
						c28e05e271 | |
| 
							
							
								 | 
						d84ba3d245 | |
| 
							
							
								
								 | 
						4e3862a331 | |
| 
							
							
								
								 | 
						157acde596 | |
| 
							
							
								 | 
						96cf7267ee | |
| 
							
							
								 | 
						481ea9ff51 | |
| 
							
							
								
								 | 
						b1204f9c6e | |
| 
							
							
								 | 
						e7f3c5850b | |
| 
							
							
								 | 
						dbb7c8d8f6 | |
| 
							
							
								
								 | 
						91dea41ca2 | |
| 
							
							
								
								 | 
						9fede70bec | |
| 
							
							
								 | 
						8a75f05859 | |
| 
							
							
								 | 
						d36666f436 | 
| 
						 | 
				
			
			@ -12,21 +12,22 @@ jobs:
 | 
			
		|||
      with:
 | 
			
		||||
        submodules: recursive
 | 
			
		||||
 | 
			
		||||
    - name: set up android development environment
 | 
			
		||||
      uses: android-actions/setup-android@v2
 | 
			
		||||
 | 
			
		||||
    - name: install dependencies
 | 
			
		||||
      run: |
 | 
			
		||||
        sdkmanager --install "cmake;3.22.1"
 | 
			
		||||
        echo "cmake.dir=/usr/local/lib/android/sdk/cmake/3.22.1" > local.properties
 | 
			
		||||
        npm install -g pnpm
 | 
			
		||||
 | 
			
		||||
    - name: Setup Java JDK
 | 
			
		||||
      uses: actions/setup-java@v4.2.1
 | 
			
		||||
      with:
 | 
			
		||||
        distribution: 'temurin'
 | 
			
		||||
        java-version: '21'
 | 
			
		||||
 | 
			
		||||
    - name: Setup Android Development Environment
 | 
			
		||||
      uses: android-actions/setup-android@v3
 | 
			
		||||
 | 
			
		||||
    - name: install dependencies
 | 
			
		||||
      run: |
 | 
			
		||||
        sdkmanager --install "cmake;3.22.1"
 | 
			
		||||
        echo "cmake.dir=$ANDROID_HOME/cmake/3.22.1" > local.properties
 | 
			
		||||
        echo "$ANDROID_HOME/build-tools/34.0.0" >> $GITHUB_PATH
 | 
			
		||||
        npm install -g pnpm
 | 
			
		||||
 | 
			
		||||
    - name: Update Submodules
 | 
			
		||||
      run: |
 | 
			
		||||
        git submodule foreach --recursive 'git pull --rebase origin main --allow-unrelated-histories'
 | 
			
		||||
| 
						 | 
				
			
			@ -34,14 +35,16 @@ jobs:
 | 
			
		|||
 | 
			
		||||
    - name: Pull Assets
 | 
			
		||||
      run: |
 | 
			
		||||
        git clone https://${{ secrets.ACCESS_TOKEN_GITHUB }}@github.com/imas-tools/gakumas-raw-txts.git app/src/main/assets/gakumas-local/gakumas-raw-txts
 | 
			
		||||
        git clone https://${{ secrets.ACCESS_TOKEN_GITHUB }}@github.com/DreamGallery/Campus-adv-txts.git app/src/main/assets/gakumas-local/gakumas-raw-txts
 | 
			
		||||
        mv app/src/main/assets/gakumas-local/gakumas-raw-txts/Resource app/src/main/assets/gakumas-local/raw
 | 
			
		||||
        rm -rf app/src/main/assets/gakumas-local/gakumas-raw-txts
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - name: Build Assets
 | 
			
		||||
      run: |
 | 
			
		||||
        mv app/src/main/assets/gakumas-local/GakumasPreTranslation/.env.sample app/src/main/assets/gakumas-local/GakumasPreTranslation/.env
 | 
			
		||||
        cd app/src/main/assets/gakumas-local && make build-resource
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - name: Write branch and commit info
 | 
			
		||||
      run: |
 | 
			
		||||
| 
						 | 
				
			
			@ -56,22 +59,44 @@ jobs:
 | 
			
		|||
      run: ./gradlew build
 | 
			
		||||
 | 
			
		||||
    - uses: actions/upload-artifact@v4
 | 
			
		||||
      id: upload_unsigned_v4
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Unsigned-apk
 | 
			
		||||
        path: app/build/outputs/apk/debug/app-debug.apk
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - uses: ilharp/sign-android-release@v1
 | 
			
		||||
    - name: Upload Unsigned APK with v3 if v4 failed
 | 
			
		||||
      if: steps.upload_unsigned_v4.outcome == 'failure'
 | 
			
		||||
      uses: actions/upload-artifact@v3
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Unsigned-apk
 | 
			
		||||
        path: app/build/outputs/apk/debug/app-debug.apk
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - uses: r0adkll/sign-android-release@v1
 | 
			
		||||
      name: Sign app APK
 | 
			
		||||
      id: sign_app
 | 
			
		||||
      with:
 | 
			
		||||
        releaseDir: app/build/outputs/apk/debug
 | 
			
		||||
        signingKey: ${{ secrets.KEYSTOREB64 }}
 | 
			
		||||
        keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }}
 | 
			
		||||
        releaseDirectory: app/build/outputs/apk/debug
 | 
			
		||||
        signingKeyBase64: ${{ secrets.KEYSTOREB64 }}
 | 
			
		||||
        alias: ${{ secrets.ANDROID_KEY_ALIAS }}
 | 
			
		||||
        keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
 | 
			
		||||
        keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
 | 
			
		||||
        buildToolsVersion: 33.0.0
 | 
			
		||||
      env:
 | 
			
		||||
        BUILD_TOOLS_VERSION: "34.0.0"
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - uses: actions/upload-artifact@v4
 | 
			
		||||
      id: upload_signed_v4
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Signed-apk
 | 
			
		||||
        path: ${{steps.sign_app.outputs.signedFile}}
 | 
			
		||||
        path: ${{steps.sign_app.outputs.signedReleaseFile}}
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - name: Upload Signed APK with v3 if v4 failed
 | 
			
		||||
      if: steps.upload_signed_v4.outcome == 'failure'
 | 
			
		||||
      uses: actions/upload-artifact@v3
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Signed-apk
 | 
			
		||||
        path: ${{steps.sign_app.outputs.signedReleaseFile}}
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,4 +15,6 @@
 | 
			
		|||
local.properties
 | 
			
		||||
/.idea
 | 
			
		||||
/.vs
 | 
			
		||||
/app
 | 
			
		||||
/.kotlin
 | 
			
		||||
/app/debug
 | 
			
		||||
/app/release
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								README.md
								
								
								
								
							
							
						
						
									
										11
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -8,16 +8,7 @@
 | 
			
		|||
# Usage
 | 
			
		||||
 | 
			
		||||
- 这是一个 XPosed 插件,已 Root 用户可以使用 [LSPosed](https://github.com/LSPosed/LSPosed),未 Root 用户可以使用 [LSPatch](https://github.com/LSPosed/LSPatch)。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO
 | 
			
		||||
 | 
			
		||||
- [x] 卡片信息、TIPS 等部分的文本 hook (`generic`)
 | 
			
		||||
- [ ] 更多类型的文件替换
 | 
			
		||||
- [ ] LSPatch 集成模式无效
 | 
			
		||||
 | 
			
		||||
... and more
 | 
			
		||||
- 安卓 15 及以上的用户,请使用 [JingMatrix/LSPosed](https://github.com/JingMatrix/LSPosed) 或 [JingMatrix/LSPatch](https://github.com/JingMatrix/LSPatch)。因为原版已停止更新。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										102
									
								
								app/build.gradle
								
								
								
								
							
							
						
						
									
										102
									
								
								app/build.gradle
								
								
								
								
							| 
						 | 
				
			
			@ -1,8 +1,10 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    id 'com.android.application'
 | 
			
		||||
    id 'org.jetbrains.kotlin.android'
 | 
			
		||||
    alias(libs.plugins.androidApplication)
 | 
			
		||||
    alias(libs.plugins.kotlinAndroid)
 | 
			
		||||
    alias(libs.plugins.compose.compiler)
 | 
			
		||||
    alias(libs.plugins.serialization)
 | 
			
		||||
    id("kotlin-parcelize")
 | 
			
		||||
}
 | 
			
		||||
android.buildFeatures.buildConfig true
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    namespace 'io.github.chinosk.gakumas.localify'
 | 
			
		||||
| 
						 | 
				
			
			@ -13,8 +15,9 @@ android {
 | 
			
		|||
        applicationId "io.github.chinosk.gakumas.localify"
 | 
			
		||||
        minSdk 29
 | 
			
		||||
        targetSdk 34
 | 
			
		||||
        versionCode 2
 | 
			
		||||
        versionName "v1.1"
 | 
			
		||||
        versionCode 12
 | 
			
		||||
        versionName "v3.1.0"
 | 
			
		||||
        buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
 | 
			
		||||
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
        vectorDrawables {
 | 
			
		||||
| 
						 | 
				
			
			@ -35,40 +38,38 @@ android {
 | 
			
		|||
            minifyEnabled false
 | 
			
		||||
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 | 
			
		||||
            buildConfigField "boolean", "ENABLE_LOG", "true"
 | 
			
		||||
            signingConfig signingConfigs.debug
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_11
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_11
 | 
			
		||||
    }
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = '1.8'
 | 
			
		||||
        jvmTarget = '11'
 | 
			
		||||
    }
 | 
			
		||||
    buildFeatures {
 | 
			
		||||
        buildConfig true
 | 
			
		||||
        compose true
 | 
			
		||||
        prefab true
 | 
			
		||||
    }
 | 
			
		||||
    composeOptions {
 | 
			
		||||
        kotlinCompilerExtensionVersion '1.5.1'
 | 
			
		||||
    }
 | 
			
		||||
    packaging {
 | 
			
		||||
        resources {
 | 
			
		||||
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    externalNativeBuild {
 | 
			
		||||
        cmake {
 | 
			
		||||
            path file('src/main/cpp/CMakeLists.txt')
 | 
			
		||||
            version '3.22.1'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    packagingOptions {
 | 
			
		||||
        pickFirst '**/libxdl.so'
 | 
			
		||||
        pickFirst '**/libshadowhook.so'
 | 
			
		||||
        exclude 'gakumas-local/gakuen-adapted-translation-data/**'
 | 
			
		||||
    }
 | 
			
		||||
    dataBinding {
 | 
			
		||||
        enable true
 | 
			
		||||
 | 
			
		||||
    packaging {
 | 
			
		||||
        jniLibs {
 | 
			
		||||
            pickFirsts += "**/libxdl.so"
 | 
			
		||||
            pickFirsts += "**/libshadowhook.so"
 | 
			
		||||
        }
 | 
			
		||||
        resources {
 | 
			
		||||
            excludes += "**/META-INF/{AL2.0,LGPL2.1}"
 | 
			
		||||
            excludes += "kotlin/**"
 | 
			
		||||
            excludes += "**.bin"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    applicationVariants.configureEach { variant ->
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +77,7 @@ android {
 | 
			
		|||
            mergeAssetsTask.doLast {
 | 
			
		||||
                delete(fileTree(dir: mergeAssetsTask.outputDir, includes: ['gakumas-local/gakuen-adapted-translation-data/**',
 | 
			
		||||
                                                                           'gakumas-local/GakumasPreTranslation/**',
 | 
			
		||||
                                                                           'gakumas-local/gakumas-generic-strings-translation/**',
 | 
			
		||||
                                                                           'gakumas-local/raw/**']))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -83,17 +85,51 @@ android {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    // implementation files('libs/lspatch_cleaned.jar')
 | 
			
		||||
    // api 'com.google.guava:guava:32.0.1-jre'
 | 
			
		||||
 | 
			
		||||
    implementation 'androidx.core:core-ktx:1.12.0'
 | 
			
		||||
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
 | 
			
		||||
    implementation platform('androidx.compose:compose-bom:2023.08.00')
 | 
			
		||||
    implementation 'androidx.compose.material3:material3'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.12.0'
 | 
			
		||||
    implementation(libs.androidx.core.ktx)
 | 
			
		||||
    implementation(libs.androidx.lifecycle.runtime.ktx)
 | 
			
		||||
    implementation(libs.androidx.material3)
 | 
			
		||||
    implementation(libs.material)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.androidx.activity.compose)
 | 
			
		||||
    implementation(libs.androidx.appcompat)
 | 
			
		||||
    implementation(libs.androidx.navigation.compose)
 | 
			
		||||
    implementation files('libs/lspatch_cleaned.jar')
 | 
			
		||||
 | 
			
		||||
    implementation 'io.github.hexhacking:xdl:2.1.1'
 | 
			
		||||
    implementation 'com.bytedance.android:shadowhook:1.0.9'
 | 
			
		||||
    compileOnly 'de.robv.android.xposed:api:82'
 | 
			
		||||
    implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.0"
 | 
			
		||||
    implementation 'com.google.code.gson:gson:2.11.0'
 | 
			
		||||
    implementation(libs.zip4j)
 | 
			
		||||
    implementation(libs.shizukuApi)
 | 
			
		||||
    implementation(libs.rikka.shizuku.provider)
 | 
			
		||||
    implementation(libs.rikka.refine)
 | 
			
		||||
    implementation(libs.rikka.hidden.stub)
 | 
			
		||||
    implementation(libs.hiddenapibypass)
 | 
			
		||||
 | 
			
		||||
    def composeBom = platform(libs.androidx.compose.bom)
 | 
			
		||||
    implementation(composeBom)
 | 
			
		||||
    androidTestImplementation(composeBom)
 | 
			
		||||
    implementation(libs.androidx.runtime)
 | 
			
		||||
    implementation(libs.androidx.material)
 | 
			
		||||
    implementation(libs.androidx.foundation)
 | 
			
		||||
    implementation(libs.androidx.foundation.layout)
 | 
			
		||||
    implementation(libs.androidx.animation)
 | 
			
		||||
    implementation(libs.androidx.ui.tooling.preview)
 | 
			
		||||
    androidTestImplementation(libs.androidx.ui.test.junit4)
 | 
			
		||||
    debugImplementation(libs.androidx.ui.tooling)
 | 
			
		||||
    debugImplementation(libs.androidx.ui.test.manifest)
 | 
			
		||||
    implementation(libs.accompanist.pager)
 | 
			
		||||
    implementation(libs.accompanist.pager.indicators)
 | 
			
		||||
    implementation(libs.androidx.lifecycle.viewmodel.compose)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.coil.compose)
 | 
			
		||||
    implementation(libs.coil.svg)
 | 
			
		||||
 | 
			
		||||
    implementation(platform(libs.okhttp.bom))
 | 
			
		||||
    implementation(libs.okhttp)
 | 
			
		||||
    implementation(libs.logging.interceptor)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.xdl)
 | 
			
		||||
    implementation(libs.shadowhook)
 | 
			
		||||
    compileOnly(libs.xposed.api)
 | 
			
		||||
    implementation(libs.kotlinx.serialization.json)
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
jar xf lspatch.jar
 | 
			
		||||
cp -r assets/lspatch/so ../../src/main/assets/lspatch/so
 | 
			
		||||
rm -rf com/google/common/util/concurrent
 | 
			
		||||
rm -rf com/google/errorprone/annotations
 | 
			
		||||
python rm_duplicate.py
 | 
			
		||||
jar cf lspatch_cleaned.jar .
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
import os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logs = """
 | 
			
		||||
Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules listenablefuture-1.0.jar -> listenablefuture-1.0 (com.google.guava:listenablefuture:1.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.CanIgnoreReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.CheckReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.CompatibleWith found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.CompileTimeConstant found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.DoNotCall found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.DoNotMock found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.ForOverride found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.FormatMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.FormatString found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.Immutable found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.IncompatibleModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.InlineMe found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.InlineMeValidationDisabled found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.Keep found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.Modifier found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.MustBeClosed found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.NoAllocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.RequiredModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.RestrictedApi found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.SuppressPackageLocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.Var found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.concurrent.GuardedBy found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.concurrent.LazyInit found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.concurrent.LockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
Duplicate class com.google.errorprone.annotations.concurrent.UnlockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
for i in logs.split("\n"):
 | 
			
		||||
    if i.startswith("Duplicate class "):
 | 
			
		||||
        flag_pos = i.find(" found in modules")
 | 
			
		||||
        if flag_pos == -1:
 | 
			
		||||
            continue
 | 
			
		||||
        class_name = i[len("Duplicate class "):flag_pos]
 | 
			
		||||
        file_name = class_name.replace(".", "/") + ".class"
 | 
			
		||||
        if not os.path.isfile(file_name):
 | 
			
		||||
            print(file_name, "not found!!!")
 | 
			
		||||
        else:
 | 
			
		||||
            os.remove(file_name)
 | 
			
		||||
            print(file_name, "removed.")
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
<lint>
 | 
			
		||||
    <issue id="ExtraTranslation" severity="ignore" />
 | 
			
		||||
</lint>
 | 
			
		||||
| 
						 | 
				
			
			@ -19,3 +19,8 @@
 | 
			
		|||
# If you keep the line number information, uncomment this to
 | 
			
		||||
# hide the original source file name.
 | 
			
		||||
#-renamesourcefileattribute SourceFile
 | 
			
		||||
 | 
			
		||||
-keep class io.github.chinosk.gakumas.localify.GakumasHookMain {
 | 
			
		||||
    <init>();
 | 
			
		||||
    native <methods>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,14 @@
 | 
			
		|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET" />
 | 
			
		||||
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
 | 
			
		||||
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
 | 
			
		||||
            tools:ignore="QueryAllPackagesPermission" />
 | 
			
		||||
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 | 
			
		||||
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:allowBackup="true"
 | 
			
		||||
        android:dataExtractionRules="@xml/data_extraction_rules"
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +18,7 @@
 | 
			
		|||
        android:label="@string/app_name"
 | 
			
		||||
        android:supportsRtl="true"
 | 
			
		||||
        android:theme="@style/Theme.GakumasLocalify"
 | 
			
		||||
        android:usesCleartextTraffic="true"
 | 
			
		||||
        tools:targetApi="31">
 | 
			
		||||
 | 
			
		||||
        <meta-data
 | 
			
		||||
| 
						 | 
				
			
			@ -32,14 +41,64 @@
 | 
			
		|||
        <activity
 | 
			
		||||
            android:name=".MainActivity"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:label="@string/app_name"
 | 
			
		||||
            android:theme="@style/Theme.GakumasLocalify">
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.MAIN" />
 | 
			
		||||
 | 
			
		||||
                <category android:name="android.intent.category.LAUNCHER" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.VIEW" />
 | 
			
		||||
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
 | 
			
		||||
                <category android:name="android.intent.category.DEFAULT" />
 | 
			
		||||
 | 
			
		||||
                <data android:scheme="content" />
 | 
			
		||||
                <data android:mimeType="application/vnd.android.package-archive" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".TranslucentActivity"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:theme="@style/Theme.GakumasLocalify.NoDisplay">
 | 
			
		||||
 | 
			
		||||
        </activity>
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
                android:name=".PatchActivity"
 | 
			
		||||
                android:exported="true"
 | 
			
		||||
                android:theme="@style/Theme.GakumasLocalify">
 | 
			
		||||
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.VIEW" />
 | 
			
		||||
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
 | 
			
		||||
                <category android:name="android.intent.category.DEFAULT" />
 | 
			
		||||
 | 
			
		||||
                <data android:scheme="content" />
 | 
			
		||||
                <data android:mimeType="application/vnd.android.package-archive" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
 | 
			
		||||
        </activity>
 | 
			
		||||
 | 
			
		||||
        <provider
 | 
			
		||||
            android:name="androidx.core.content.FileProvider"
 | 
			
		||||
            android:authorities="${applicationId}.fileprovider"
 | 
			
		||||
            android:exported="false"
 | 
			
		||||
            android:grantUriPermissions="true">
 | 
			
		||||
            <meta-data
 | 
			
		||||
                android:name="android.support.FILE_PROVIDER_PATHS"
 | 
			
		||||
                android:resource="@xml/file_paths" />
 | 
			
		||||
        </provider>
 | 
			
		||||
 | 
			
		||||
        <provider
 | 
			
		||||
                android:name="rikka.shizuku.ShizukuProvider"
 | 
			
		||||
                android:authorities="${applicationId}.shizuku"
 | 
			
		||||
                android:multiprocess="false"
 | 
			
		||||
                android:enabled="true"
 | 
			
		||||
                android:exported="true"
 | 
			
		||||
                android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
 | 
			
		||||
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
{
 | 
			
		||||
    "plugin_repo": "https://github.com/chinosk6/gakuen-imas-localify",
 | 
			
		||||
    "main_contributors": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "chinosk (Plugin code)",
 | 
			
		||||
            "links": [
 | 
			
		||||
                {"name": "Github", "link": "https://github.com/chinosk6"},
 | 
			
		||||
                {"name": "Bilibili", "link": "https://space.bilibili.com/287061163"}
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "DarwinTree (Translation Workflow)",
 | 
			
		||||
            "links": [
 | 
			
		||||
                {"name": "Github", "link": "https://github.com/darwintree"},
 | 
			
		||||
                {"name": "Bilibili", "link": "https://space.bilibili.com/6069705"}
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "And all other translators",
 | 
			
		||||
            "links": [
 | 
			
		||||
                {"name": "Github", "link": "https://github.com/chinosk6/GakumasTranslationData/graphs/contributors"}
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "contrib_img": {
 | 
			
		||||
        "plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
 | 
			
		||||
        "translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
{
 | 
			
		||||
    "plugin_repo": "https://github.com/chinosk6/gakuen-imas-localify",
 | 
			
		||||
    "main_contributors": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "chinosk(插件本体)",
 | 
			
		||||
            "links": [
 | 
			
		||||
                {"name": "Github", "link": "https://github.com/chinosk6"},
 | 
			
		||||
                {"name": "Bilibili", "link": "https://space.bilibili.com/287061163"}
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "DarwinTree(译文工作流)",
 | 
			
		||||
            "links": [
 | 
			
		||||
                {"name": "Github", "link": "https://github.com/darwintree"},
 | 
			
		||||
                {"name": "Bilibili", "link": "https://space.bilibili.com/6069705"}
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "以及其他所有翻译贡献者",
 | 
			
		||||
            "links": [
 | 
			
		||||
                {"name": "Github", "link": "https://github.com/chinosk6/GakumasTranslationData/graphs/contributors"}
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "contrib_img": {
 | 
			
		||||
        "plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
 | 
			
		||||
        "translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData",
 | 
			
		||||
        "translations": [
 | 
			
		||||
            "https://contrib.rocks/image?repo=imas-tools/gakuen-adapted-translation-data",
 | 
			
		||||
            "https://contrib.rocks/image?repo=imas-tools/gakumas-generic-strings-translation"
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit 0ffe615be44269500cfb9fa78b967a791724535c
 | 
			
		||||
Subproject commit df448152cfb55c528d66832b2470ff1c2277e980
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -42,9 +42,11 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
 | 
			
		|||
        GakumasLocalify/Log.cpp
 | 
			
		||||
        GakumasLocalify/Misc.cpp
 | 
			
		||||
        GakumasLocalify/Local.cpp
 | 
			
		||||
        GakumasLocalify/MasterLocal.cpp
 | 
			
		||||
        GakumasLocalify/camera/baseCamera.cpp
 | 
			
		||||
        GakumasLocalify/camera/camera.cpp
 | 
			
		||||
        GakumasLocalify/config/Config.cpp
 | 
			
		||||
        GakumasLocalify/string_parser/StringParser.cpp
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
target_link_libraries(${CMAKE_PROJECT_NAME} xdl::xdl)
 | 
			
		||||
| 
						 | 
				
			
			@ -53,12 +55,17 @@ target_link_libraries(${CMAKE_PROJECT_NAME} shadowhook::shadowhook)
 | 
			
		|||
include_directories(GakumasLocalify)
 | 
			
		||||
include_directories(${CMAKE_SOURCE_DIR}/deps)
 | 
			
		||||
 | 
			
		||||
set(FMT_DIR "${CMAKE_SOURCE_DIR}/deps/fmt-11.0.2")
 | 
			
		||||
include_directories(${FMT_DIR}/include)
 | 
			
		||||
add_library(fmt STATIC ${FMT_DIR}/src/format.cc)
 | 
			
		||||
 | 
			
		||||
# Specifies libraries CMake should link to your target library. You
 | 
			
		||||
# can link libraries from various origins, such as libraries defined in this
 | 
			
		||||
# build script, prebuilt third-party libraries, or Android system libraries.
 | 
			
		||||
target_link_libraries(${CMAKE_PROJECT_NAME}
 | 
			
		||||
    # List libraries link to the target library
 | 
			
		||||
    android
 | 
			
		||||
    log)
 | 
			
		||||
    log
 | 
			
		||||
    fmt)
 | 
			
		||||
 | 
			
		||||
target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_23)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,29 +1,58 @@
 | 
			
		|||
#define KEY_W  51
 | 
			
		||||
#define KEY_S  47
 | 
			
		||||
#define KEY_A  29
 | 
			
		||||
#define KEY_D  32
 | 
			
		||||
#define KEY_R  46
 | 
			
		||||
#define KEY_Q  45
 | 
			
		||||
#define KEY_E  33
 | 
			
		||||
#define KEY_F  34
 | 
			
		||||
#define KEY_I  37
 | 
			
		||||
#define KEY_K  39
 | 
			
		||||
#define KEY_J  38
 | 
			
		||||
#define KEY_L  40
 | 
			
		||||
#define KEY_V  50
 | 
			
		||||
#define KEY_UP  19
 | 
			
		||||
#define KEY_DOWN  20
 | 
			
		||||
#define KEY_LEFT  21
 | 
			
		||||
#define KEY_RIGHT  22
 | 
			
		||||
#define KEY_CTRL  113
 | 
			
		||||
#define KEY_SHIFT  59
 | 
			
		||||
#define KEY_ALT  57
 | 
			
		||||
#define KEY_SPACE  62
 | 
			
		||||
#define KEY_ADD  70
 | 
			
		||||
#define KEY_SUB  69
 | 
			
		||||
#include "../platformDefine.hpp"
 | 
			
		||||
 | 
			
		||||
#define WM_KEYDOWN 0
 | 
			
		||||
#define WM_KEYUP 1
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
	#define KEY_W  51
 | 
			
		||||
	#define KEY_S  47
 | 
			
		||||
	#define KEY_A  29
 | 
			
		||||
	#define KEY_D  32
 | 
			
		||||
	#define KEY_R  46
 | 
			
		||||
	#define KEY_Q  45
 | 
			
		||||
	#define KEY_E  33
 | 
			
		||||
	#define KEY_F  34
 | 
			
		||||
	#define KEY_I  37
 | 
			
		||||
	#define KEY_K  39
 | 
			
		||||
	#define KEY_J  38
 | 
			
		||||
	#define KEY_L  40
 | 
			
		||||
	#define KEY_V  50
 | 
			
		||||
	#define KEY_UP  19
 | 
			
		||||
	#define KEY_DOWN  20
 | 
			
		||||
	#define KEY_LEFT  21
 | 
			
		||||
	#define KEY_RIGHT  22
 | 
			
		||||
	#define KEY_CTRL  113
 | 
			
		||||
	#define KEY_SHIFT  59
 | 
			
		||||
	#define KEY_ALT  57
 | 
			
		||||
	#define KEY_SPACE  62
 | 
			
		||||
	#define KEY_ADD  70
 | 
			
		||||
	#define KEY_SUB  69
 | 
			
		||||
 | 
			
		||||
	#define WM_KEYDOWN 0
 | 
			
		||||
	#define WM_KEYUP 1
 | 
			
		||||
#else
 | 
			
		||||
	#define KEY_W  'W'
 | 
			
		||||
	#define KEY_S  'S'
 | 
			
		||||
	#define KEY_A  'A'
 | 
			
		||||
	#define KEY_D  'D'
 | 
			
		||||
	#define KEY_R  'R'
 | 
			
		||||
	#define KEY_Q  'Q'
 | 
			
		||||
	#define KEY_E  'E'
 | 
			
		||||
	#define KEY_F  'F'
 | 
			
		||||
	#define KEY_I  'I'
 | 
			
		||||
	#define KEY_K  'K'
 | 
			
		||||
	#define KEY_J  'J'
 | 
			
		||||
	#define KEY_L  'L'
 | 
			
		||||
	#define KEY_V  'V'
 | 
			
		||||
	#define KEY_UP  38
 | 
			
		||||
	#define KEY_DOWN  40
 | 
			
		||||
	#define KEY_LEFT  37
 | 
			
		||||
	#define KEY_RIGHT  39
 | 
			
		||||
	#define KEY_CTRL  17
 | 
			
		||||
	#define KEY_SHIFT  16
 | 
			
		||||
	#define KEY_ALT  18
 | 
			
		||||
	#define KEY_SPACE  32
 | 
			
		||||
 | 
			
		||||
	#define KEY_ADD  187
 | 
			
		||||
	#define KEY_SUB  189
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define BTN_A 96
 | 
			
		||||
#define BTN_B 97
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -14,14 +14,96 @@ namespace Il2cppUtils {
 | 
			
		|||
        const char* namespaze;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct Il2CppObject
 | 
			
		||||
    {
 | 
			
		||||
        union
 | 
			
		||||
        {
 | 
			
		||||
            void* klass;
 | 
			
		||||
            void* vtable;
 | 
			
		||||
        };
 | 
			
		||||
        void* monitor;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    enum Il2CppTypeEnum
 | 
			
		||||
    {
 | 
			
		||||
        IL2CPP_TYPE_END = 0x00,       /* End of List */
 | 
			
		||||
        IL2CPP_TYPE_VOID = 0x01,
 | 
			
		||||
        IL2CPP_TYPE_BOOLEAN = 0x02,
 | 
			
		||||
        IL2CPP_TYPE_CHAR = 0x03,
 | 
			
		||||
        IL2CPP_TYPE_I1 = 0x04,
 | 
			
		||||
        IL2CPP_TYPE_U1 = 0x05,
 | 
			
		||||
        IL2CPP_TYPE_I2 = 0x06,
 | 
			
		||||
        IL2CPP_TYPE_U2 = 0x07,
 | 
			
		||||
        IL2CPP_TYPE_I4 = 0x08,
 | 
			
		||||
        IL2CPP_TYPE_U4 = 0x09,
 | 
			
		||||
        IL2CPP_TYPE_I8 = 0x0a,
 | 
			
		||||
        IL2CPP_TYPE_U8 = 0x0b,
 | 
			
		||||
        IL2CPP_TYPE_R4 = 0x0c,
 | 
			
		||||
        IL2CPP_TYPE_R8 = 0x0d,
 | 
			
		||||
        IL2CPP_TYPE_STRING = 0x0e,
 | 
			
		||||
        IL2CPP_TYPE_PTR = 0x0f,
 | 
			
		||||
        IL2CPP_TYPE_BYREF = 0x10,
 | 
			
		||||
        IL2CPP_TYPE_VALUETYPE = 0x11,
 | 
			
		||||
        IL2CPP_TYPE_CLASS = 0x12,
 | 
			
		||||
        IL2CPP_TYPE_VAR = 0x13,
 | 
			
		||||
        IL2CPP_TYPE_ARRAY = 0x14,
 | 
			
		||||
        IL2CPP_TYPE_GENERICINST = 0x15,
 | 
			
		||||
        IL2CPP_TYPE_TYPEDBYREF = 0x16,
 | 
			
		||||
        IL2CPP_TYPE_I = 0x18,
 | 
			
		||||
        IL2CPP_TYPE_U = 0x19,
 | 
			
		||||
        IL2CPP_TYPE_FNPTR = 0x1b,
 | 
			
		||||
        IL2CPP_TYPE_OBJECT = 0x1c,
 | 
			
		||||
        IL2CPP_TYPE_SZARRAY = 0x1d,
 | 
			
		||||
        IL2CPP_TYPE_MVAR = 0x1e,
 | 
			
		||||
        IL2CPP_TYPE_CMOD_REQD = 0x1f,
 | 
			
		||||
        IL2CPP_TYPE_CMOD_OPT = 0x20,
 | 
			
		||||
        IL2CPP_TYPE_INTERNAL = 0x21,
 | 
			
		||||
 | 
			
		||||
        IL2CPP_TYPE_MODIFIER = 0x40,
 | 
			
		||||
        IL2CPP_TYPE_SENTINEL = 0x41,
 | 
			
		||||
        IL2CPP_TYPE_PINNED = 0x45,
 | 
			
		||||
 | 
			
		||||
        IL2CPP_TYPE_ENUM = 0x55
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    typedef struct Il2CppType
 | 
			
		||||
    {
 | 
			
		||||
        void* dummy;
 | 
			
		||||
        unsigned int attrs : 16;
 | 
			
		||||
        Il2CppTypeEnum type : 8;
 | 
			
		||||
        unsigned int num_mods : 6;
 | 
			
		||||
        unsigned int byref : 1;
 | 
			
		||||
        unsigned int pinned : 1;
 | 
			
		||||
    } Il2CppType;
 | 
			
		||||
 | 
			
		||||
    struct Il2CppReflectionType
 | 
			
		||||
    {
 | 
			
		||||
        Il2CppObject object;
 | 
			
		||||
        const Il2CppType* type;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct Resolution_t {
 | 
			
		||||
        int width;
 | 
			
		||||
        int height;
 | 
			
		||||
        int herz;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct FieldInfo {
 | 
			
		||||
        const char* name;
 | 
			
		||||
        const Il2CppType* type;
 | 
			
		||||
        uintptr_t parent;
 | 
			
		||||
        int32_t offset;
 | 
			
		||||
        uint32_t token;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct MethodInfo {
 | 
			
		||||
        uintptr_t methodPointer;
 | 
			
		||||
        uintptr_t invoker_method;
 | 
			
		||||
        const char* name;
 | 
			
		||||
        uintptr_t klass;
 | 
			
		||||
        //const Il2CppType* return_type;
 | 
			
		||||
        const Il2CppType* return_type;
 | 
			
		||||
        //const ParameterInfo* parameters;
 | 
			
		||||
        const void* return_type;
 | 
			
		||||
        // const void* return_type;
 | 
			
		||||
        const void* parameters;
 | 
			
		||||
        uintptr_t methodDefinition;
 | 
			
		||||
        uintptr_t genericContainer;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,13 +118,7 @@ namespace Il2cppUtils {
 | 
			
		|||
        uint8_t is_marshaled_from_native : 1;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct Resolution_t {
 | 
			
		||||
        int width;
 | 
			
		||||
        int height;
 | 
			
		||||
        int herz;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
 | 
			
		||||
    static UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
 | 
			
		||||
                   const std::string& className) {
 | 
			
		||||
        const auto assembly = UnityResolve::Get(assemblyName);
 | 
			
		||||
        if (!assembly) {
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +157,7 @@ namespace Il2cppUtils {
 | 
			
		|||
        return ret;
 | 
			
		||||
    }*/
 | 
			
		||||
 | 
			
		||||
    UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName,
 | 
			
		||||
    static UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName,
 | 
			
		||||
                           const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
 | 
			
		||||
        const auto assembly = UnityResolve::Get(assemblyName);
 | 
			
		||||
        if (!assembly) {
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +184,7 @@ namespace Il2cppUtils {
 | 
			
		|||
        return method;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName,
 | 
			
		||||
    static void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName,
 | 
			
		||||
                           const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
 | 
			
		||||
        auto method = GetMethod(assemblyName, nameSpaceName, className, methodName, args);
 | 
			
		||||
        if (method) {
 | 
			
		||||
| 
						 | 
				
			
			@ -117,20 +193,28 @@ namespace Il2cppUtils {
 | 
			
		|||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void* il2cpp_resolve_icall(const char* s) {
 | 
			
		||||
    static void* il2cpp_resolve_icall(const char* s) {
 | 
			
		||||
        return UnityResolve::Invoke<void*>("il2cpp_resolve_icall", s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Il2CppClassHead* get_class_from_instance(const void* instance) {
 | 
			
		||||
    static Il2CppClassHead* get_class_from_instance(const void* instance) {
 | 
			
		||||
        return static_cast<Il2CppClassHead*>(*static_cast<void* const*>(std::assume_aligned<alignof(void*)>(instance)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) {
 | 
			
		||||
    static MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) {
 | 
			
		||||
        return UnityResolve::Invoke<MethodInfo*>("il2cpp_class_get_method_from_name", klass, name, argsCount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate)
 | 
			
		||||
    {
 | 
			
		||||
    static uintptr_t il2cpp_class_get_method_pointer_from_name(void* klass, const char* name, int argsCount) {
 | 
			
		||||
        auto findKlass = il2cpp_class_get_method_from_name(klass, name, argsCount);
 | 
			
		||||
        if (findKlass) {
 | 
			
		||||
            return findKlass->methodPointer;
 | 
			
		||||
        }
 | 
			
		||||
        Log::ErrorFmt("method: %s not found", name);
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate) {
 | 
			
		||||
        void* iter{};
 | 
			
		||||
        while (const auto curNestedClass = UnityResolve::Invoke<void*>("il2cpp_class_get_nested_types", klass, &iter))
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -143,22 +227,267 @@ namespace Il2cppUtils {
 | 
			
		|||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void* find_nested_class_from_name(void* klass, const char* name)
 | 
			
		||||
    {
 | 
			
		||||
    static void* find_nested_class_from_name(void* klass, const char* name) {
 | 
			
		||||
        return find_nested_class(klass, [name = std::string_view(name)](void* nestedClass) {
 | 
			
		||||
            return static_cast<Il2CppClassHead*>(nestedClass)->name == name;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename RType>
 | 
			
		||||
    auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType {
 | 
			
		||||
    static auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType {
 | 
			
		||||
        return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename RType>
 | 
			
		||||
    auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, RType value) -> void {
 | 
			
		||||
    static auto ClassGetFieldValue(void* obj, FieldInfo* field) -> RType {
 | 
			
		||||
        return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    static auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, T value) -> void {
 | 
			
		||||
        const auto fieldPtr = static_cast<std::byte*>(obj) + field->offset;
 | 
			
		||||
        std::memcpy(fieldPtr, std::addressof(value), sizeof(T));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename RType>
 | 
			
		||||
    static auto ClassSetFieldValue(void* obj, FieldInfo* field, RType value) -> void {
 | 
			
		||||
        *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset) = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void* get_system_class_from_reflection_type_str(const char* typeStr, const char* assemblyName = "mscorlib") {
 | 
			
		||||
        using Il2CppString = UnityResolve::UnityType::String;
 | 
			
		||||
 | 
			
		||||
        static auto assemblyLoad = reinterpret_cast<void* (*)(Il2CppString*)>(
 | 
			
		||||
                GetMethodPointer("mscorlib.dll", "System.Reflection",
 | 
			
		||||
                                 "Assembly", "Load", {"*"})
 | 
			
		||||
        );
 | 
			
		||||
        static auto assemblyGetType = reinterpret_cast<Il2CppReflectionType * (*)(void*, Il2CppString*)>(
 | 
			
		||||
                GetMethodPointer("mscorlib.dll", "System.Reflection",
 | 
			
		||||
                                 "Assembly", "GetType", {"*"})
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        static auto reflectionAssembly = assemblyLoad(Il2CppString::New(assemblyName));
 | 
			
		||||
        auto reflectionType = assemblyGetType(reflectionAssembly, Il2CppString::New(typeStr));
 | 
			
		||||
        return UnityResolve::Invoke<void*>("il2cpp_class_from_system_type", reflectionType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static std::unordered_map<std::string, std::unordered_map<int, std::string>> enumToValueMapCache{};
 | 
			
		||||
    static std::unordered_map<int, std::string> EnumToValueMap(Il2CppClassHead* enumClass, bool useCache) {
 | 
			
		||||
        std::unordered_map<int, std::string> ret{};
 | 
			
		||||
        auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", enumClass);
 | 
			
		||||
 | 
			
		||||
        if (isEnum) {
 | 
			
		||||
            Il2cppUtils::FieldInfo* field = nullptr;
 | 
			
		||||
            void* iter = nullptr;
 | 
			
		||||
 | 
			
		||||
            std::string cacheName = std::string(enumClass->namespaze) + "::" + enumClass->name;
 | 
			
		||||
            if (useCache) {
 | 
			
		||||
                if (auto it = enumToValueMapCache.find(cacheName); it != enumToValueMapCache.end()) {
 | 
			
		||||
                    return it->second;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            while ((field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>("il2cpp_class_get_fields", enumClass, &iter))) {
 | 
			
		||||
                // Log::DebugFmt("field: %s, off: %d", field->name, field->offset);
 | 
			
		||||
                if (field->offset > 0) continue;  // 非 static
 | 
			
		||||
                if (strcmp(field->name, "value__") == 0) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                int value;
 | 
			
		||||
                UnityResolve::Invoke<void>("il2cpp_field_static_get_value", field, &value);
 | 
			
		||||
                // Log::DebugFmt("returnClass: %s - %s: 0x%x", enumClass->name, field->name, value);
 | 
			
		||||
                std::string itemName = std::string(enumClass->name) + "_" + field->name;
 | 
			
		||||
                ret.emplace(value, std::move(itemName));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (useCache) {
 | 
			
		||||
                enumToValueMapCache.emplace(std::move(cacheName), ret);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename T = void*>
 | 
			
		||||
    static void iterate_IEnumerable(const void* obj, std::invocable<T> auto&& receiver)
 | 
			
		||||
    {
 | 
			
		||||
        const auto klass = get_class_from_instance(obj);
 | 
			
		||||
        const auto getEnumeratorMethod = reinterpret_cast<void* (*)(const void*)>(il2cpp_class_get_method_from_name(klass, "GetEnumerator", 0)->methodPointer);
 | 
			
		||||
        const auto enumerator = getEnumeratorMethod(obj);
 | 
			
		||||
        const auto enumeratorClass = get_class_from_instance(enumerator);
 | 
			
		||||
        const auto getCurrentMethod = reinterpret_cast<T(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "get_Current", 0)->methodPointer);
 | 
			
		||||
        const auto moveNextMethod = reinterpret_cast<bool(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "MoveNext", 0)->methodPointer);
 | 
			
		||||
 | 
			
		||||
        while (moveNextMethod(enumerator))
 | 
			
		||||
        {
 | 
			
		||||
            static_cast<decltype(receiver)>(receiver)(getCurrentMethod(enumerator));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    namespace Tools {
 | 
			
		||||
 | 
			
		||||
        template <typename T = void*>
 | 
			
		||||
        class CSListEditor {
 | 
			
		||||
        public:
 | 
			
		||||
            CSListEditor(void* list) {
 | 
			
		||||
                list_klass = get_class_from_instance(list);
 | 
			
		||||
                lst = list;
 | 
			
		||||
 | 
			
		||||
                lst_get_Count_method = il2cpp_class_get_method_from_name(list_klass, "get_Count", 0);
 | 
			
		||||
                lst_get_Item_method = il2cpp_class_get_method_from_name(list_klass, "get_Item", 1);
 | 
			
		||||
                lst_set_Item_method = il2cpp_class_get_method_from_name(list_klass, "set_Item", 2);
 | 
			
		||||
                lst_Add_method = il2cpp_class_get_method_from_name(list_klass, "Add", 1);
 | 
			
		||||
                lst_Contains_method = il2cpp_class_get_method_from_name(list_klass, "Contains", 1);
 | 
			
		||||
 | 
			
		||||
                lst_get_Count = reinterpret_cast<lst_get_Count_t>(lst_get_Count_method->methodPointer);
 | 
			
		||||
                lst_get_Item = reinterpret_cast<lst_get_Item_t>(lst_get_Item_method->methodPointer);
 | 
			
		||||
                lst_set_Item = reinterpret_cast<lst_set_Item_t>(lst_set_Item_method->methodPointer);
 | 
			
		||||
                lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer);
 | 
			
		||||
                lst_Contains = reinterpret_cast<lst_Contains_t>(lst_Contains_method->methodPointer);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void Add(T value) {
 | 
			
		||||
                lst_Add(lst, value, lst_Add_method);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool Contains(T value) {
 | 
			
		||||
                return lst_Contains(lst, value, lst_Contains_method);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            T get_Item(int index) {
 | 
			
		||||
                return lst_get_Item(lst, index, lst_get_Item_method);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void set_Item(int index, T value) {
 | 
			
		||||
                return lst_set_Item(lst, index, value, lst_set_Item_method);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            int get_Count() {
 | 
			
		||||
                return lst_get_Count(lst, lst_get_Count_method);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            T operator[] (int key) {
 | 
			
		||||
                return get_Item(key);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            class Iterator {
 | 
			
		||||
            public:
 | 
			
		||||
                Iterator(CSListEditor<T>* editor, int index) : editor(editor), index(index) {}
 | 
			
		||||
 | 
			
		||||
                T operator*() const {
 | 
			
		||||
                    return editor->get_Item(index);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Iterator& operator++() {
 | 
			
		||||
                    ++index;
 | 
			
		||||
                    return *this;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                bool operator!=(const Iterator& other) const {
 | 
			
		||||
                    return index != other.index;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            private:
 | 
			
		||||
                CSListEditor<T>* editor;
 | 
			
		||||
                int index;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Iterator begin() {
 | 
			
		||||
                return Iterator(this, 0);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Iterator end() {
 | 
			
		||||
                return Iterator(this, get_Count());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void* lst;
 | 
			
		||||
            void* list_klass;
 | 
			
		||||
        private:
 | 
			
		||||
            typedef T(*lst_get_Item_t)(void*, int, void* mtd);
 | 
			
		||||
            typedef void(*lst_Add_t)(void*, T, void* mtd);
 | 
			
		||||
            typedef void(*lst_set_Item_t)(void*, int, T, void* mtd);
 | 
			
		||||
            typedef int(*lst_get_Count_t)(void*, void* mtd);
 | 
			
		||||
            typedef bool(*lst_Contains_t)(void*, T, void* mtd);
 | 
			
		||||
 | 
			
		||||
            MethodInfo* lst_get_Item_method;
 | 
			
		||||
            MethodInfo* lst_Add_method;
 | 
			
		||||
            MethodInfo* lst_get_Count_method;
 | 
			
		||||
            MethodInfo* lst_set_Item_method;
 | 
			
		||||
            MethodInfo* lst_Contains_method;
 | 
			
		||||
 | 
			
		||||
            lst_get_Item_t lst_get_Item;
 | 
			
		||||
            lst_set_Item_t lst_set_Item;
 | 
			
		||||
            lst_Add_t lst_Add;
 | 
			
		||||
            lst_get_Count_t lst_get_Count;
 | 
			
		||||
            lst_Contains_t lst_Contains;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        template <typename KT = void*, typename VT = void*>
 | 
			
		||||
        class CSDictEditor {
 | 
			
		||||
        public:
 | 
			
		||||
            // @param dict: Dictionary instance.
 | 
			
		||||
            // @param dictTypeStr: Reflection type. eg: "System.Collections.Generic.Dictionary`2[System.Int32, System.Int32]"
 | 
			
		||||
            CSDictEditor(void* dict, const char* dictTypeStr) {
 | 
			
		||||
                dic_klass = Il2cppUtils::get_system_class_from_reflection_type_str(dictTypeStr);
 | 
			
		||||
				initDict(dict);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CSDictEditor(void* dict) {
 | 
			
		||||
				dic_klass = get_class_from_instance(dict);
 | 
			
		||||
				initDict(dict);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CSDictEditor(void* dict, void* dicClass) {
 | 
			
		||||
                dic_klass = dicClass;
 | 
			
		||||
				initDict(dict);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void Add(KT key, VT value) {
 | 
			
		||||
                dic_Add(dict, key, value, Add_method);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool ContainsKey(KT key) {
 | 
			
		||||
                return dic_containsKey(dict, key, ContainsKey_method);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            VT get_Item(KT key) {
 | 
			
		||||
                return dic_get_Item(dict, key, get_Item_method);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            VT operator[] (KT key) {
 | 
			
		||||
                return get_Item(key);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void* dict;
 | 
			
		||||
            void* dic_klass;
 | 
			
		||||
 | 
			
		||||
        private:
 | 
			
		||||
            void initDict(void* dict) {
 | 
			
		||||
				// dic_klass = dicClass;
 | 
			
		||||
                this->dict = dict;
 | 
			
		||||
 | 
			
		||||
                get_Item_method = il2cpp_class_get_method_from_name(dic_klass, "get_Item", 1);
 | 
			
		||||
                Add_method = il2cpp_class_get_method_from_name(dic_klass, "Add", 2);
 | 
			
		||||
                ContainsKey_method = il2cpp_class_get_method_from_name(dic_klass, "ContainsKey", 1);
 | 
			
		||||
 | 
			
		||||
                dic_get_Item = (dic_get_Item_t)get_Item_method->methodPointer;
 | 
			
		||||
                dic_Add = (dic_Add_t)Add_method->methodPointer;
 | 
			
		||||
                dic_containsKey = (dic_containsKey_t)ContainsKey_method->methodPointer;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            typedef VT(*dic_get_Item_t)(void*, KT, void* mtd);
 | 
			
		||||
            typedef VT(*dic_Add_t)(void*, KT, VT, void* mtd);
 | 
			
		||||
            typedef VT(*dic_containsKey_t)(void*, KT, void* mtd);
 | 
			
		||||
 | 
			
		||||
            CSDictEditor();
 | 
			
		||||
            MethodInfo* get_Item_method;
 | 
			
		||||
            MethodInfo* Add_method;
 | 
			
		||||
            MethodInfo* ContainsKey_method;
 | 
			
		||||
            dic_get_Item_t dic_get_Item;
 | 
			
		||||
            dic_Add_t dic_Add;
 | 
			
		||||
            dic_containsKey_t dic_containsKey;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,23 +11,80 @@
 | 
			
		|||
#include <thread>
 | 
			
		||||
#include <regex>
 | 
			
		||||
#include <ranges>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <cctype>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include "BaseDefine.h"
 | 
			
		||||
#include "string_parser/StringParser.hpp"
 | 
			
		||||
 | 
			
		||||
// #include "cpprest/details/http_helpers.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::Local {
 | 
			
		||||
    std::unordered_map<std::string, std::string> i18nData{};
 | 
			
		||||
    std::unordered_map<std::string, std::string> i18nDumpData{};
 | 
			
		||||
    std::unordered_map<std::string, std::string> genericText{};
 | 
			
		||||
    std::unordered_map<std::string, std::string> genericSplitText{};
 | 
			
		||||
    std::unordered_map<std::string, std::string> genericFmtText{};
 | 
			
		||||
    std::vector<std::string> genericTextDumpData{};
 | 
			
		||||
    std::vector<std::string> genericSplittedDumpData{};
 | 
			
		||||
    std::vector<std::string> genericOrigTextDumpData{};
 | 
			
		||||
    std::vector<std::string> genericFmtTextDumpData{};
 | 
			
		||||
 | 
			
		||||
    std::unordered_set<std::string> translatedText{};
 | 
			
		||||
    int genericDumpFileIndex = 0;
 | 
			
		||||
    const std::string splitTextPrefix = "[__split__]";
 | 
			
		||||
 | 
			
		||||
    std::filesystem::path GetBasePath() {
 | 
			
		||||
        return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string trim(const std::string& str) {
 | 
			
		||||
        auto is_not_space = [](char ch) { return !std::isspace(ch); };
 | 
			
		||||
        auto start = std::ranges::find_if(str, is_not_space);
 | 
			
		||||
        auto end = std::ranges::find_if(str | std::views::reverse, is_not_space).base();
 | 
			
		||||
 | 
			
		||||
        if (start < end) {
 | 
			
		||||
            return {start, end};
 | 
			
		||||
        }
 | 
			
		||||
        return "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string findInMapIgnoreSpace(const std::string& key, const std::unordered_map<std::string, std::string>& searchMap) {
 | 
			
		||||
        auto is_space = [](char ch) { return std::isspace(ch); };
 | 
			
		||||
        auto front = std::ranges::find_if_not(key, is_space);
 | 
			
		||||
        auto back = std::ranges::find_if_not(key | std::views::reverse, is_space).base();
 | 
			
		||||
 | 
			
		||||
        std::string prefix(key.begin(), front);
 | 
			
		||||
        std::string suffix(back, key.end());
 | 
			
		||||
 | 
			
		||||
        std::string trimmedKey = trim(key);
 | 
			
		||||
        if ( auto it = searchMap.find(trimmedKey); it != searchMap.end()) {
 | 
			
		||||
            return prefix + it->second + suffix;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enum class DumpStrStat {
 | 
			
		||||
        DEFAULT = 0,
 | 
			
		||||
        SPLITTABLE_ORIG = 1,
 | 
			
		||||
        SPLITTED = 2,
 | 
			
		||||
        FMT = 3
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    enum class SplitTagsTranslationStat {
 | 
			
		||||
        NO_TRANS,
 | 
			
		||||
        PART_TRANS,
 | 
			
		||||
        FULL_TRANS,
 | 
			
		||||
        NO_SPLIT,
 | 
			
		||||
        NO_SPLIT_AND_EMPTY
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    void LoadJsonDataToMap(const std::filesystem::path& filePath, std::unordered_map<std::string, std::string>& dict,
 | 
			
		||||
                           const bool insertToTranslated = false, const bool needClearDict = true) {
 | 
			
		||||
                           const bool insertToTranslated = false, const bool needClearDict = true,
 | 
			
		||||
                           const bool needCheckSplitPrefix = false) {
 | 
			
		||||
        if (!exists(filePath)) return;
 | 
			
		||||
        try {
 | 
			
		||||
            if (needClearDict) {
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +92,7 @@ namespace GakumasLocal::Local {
 | 
			
		|||
            }
 | 
			
		||||
            std::ifstream file(filePath);
 | 
			
		||||
            if (!file.is_open()) {
 | 
			
		||||
                Log::ErrorFmt("Load %s failed.\n", filePath.c_str());
 | 
			
		||||
                Log::ErrorFmt("Load %s failed.\n", filePath.string().c_str());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
 | 
			
		||||
| 
						 | 
				
			
			@ -44,12 +101,20 @@ namespace GakumasLocal::Local {
 | 
			
		|||
            for (auto& i : fileData.items()) {
 | 
			
		||||
                const auto& key = i.key();
 | 
			
		||||
                const std::string value = i.value();
 | 
			
		||||
                if (insertToTranslated) translatedText.emplace(value);
 | 
			
		||||
                dict[key] = value;
 | 
			
		||||
                if (needCheckSplitPrefix && key.starts_with(splitTextPrefix) && value.starts_with(splitTextPrefix)) {
 | 
			
		||||
                    static const auto splitTextPrefixLength = splitTextPrefix.size();
 | 
			
		||||
                    const auto splitValue = value.substr(splitTextPrefixLength);
 | 
			
		||||
                    genericSplitText[key.substr(splitTextPrefixLength)] = splitValue;
 | 
			
		||||
                    if (insertToTranslated) translatedText.emplace(splitValue);
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    dict[key] = value;
 | 
			
		||||
                    if (insertToTranslated) translatedText.emplace(value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (std::exception& e) {
 | 
			
		||||
            Log::ErrorFmt("Load %s failed: %s\n", filePath.c_str(), e.what());
 | 
			
		||||
            Log::ErrorFmt("Load %s failed: %s\n", filePath.string().c_str(), e.what());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +149,7 @@ namespace GakumasLocal::Local {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    void DumpVectorDataToJson(const std::filesystem::path& dumpBasePath, const std::filesystem::path& fileName,
 | 
			
		||||
                           const std::vector<std::string>& vec) {
 | 
			
		||||
                           const std::vector<std::string>& vec, const std::string& prefix = "") {
 | 
			
		||||
        const auto dumpFilePath = dumpBasePath / fileName;
 | 
			
		||||
        try {
 | 
			
		||||
            if (!is_directory(dumpBasePath)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +166,12 @@ namespace GakumasLocal::Local {
 | 
			
		|||
            dumpLrcFile.close();
 | 
			
		||||
            auto fileData = nlohmann::ordered_json::parse(fileContent);
 | 
			
		||||
            for (const auto& i : vec) {
 | 
			
		||||
                fileData[i] = i;
 | 
			
		||||
                if (!prefix.empty()) {
 | 
			
		||||
                    fileData[prefix + i] = prefix + i;
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    fileData[i] = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            const auto newStr = fileData.dump(4, 32, false);
 | 
			
		||||
            std::ofstream dumpWriteLrcFile(dumpFilePath, std::ofstream::out);
 | 
			
		||||
| 
						 | 
				
			
			@ -181,7 +251,7 @@ namespace GakumasLocal::Local {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    bool GetSplitTagsTranslation(const std::string& origText, std::string* newText, std::vector<std::string>& unTransResultRet) {
 | 
			
		||||
        if (!origText.contains(L'<')) return false;
 | 
			
		||||
        if (!origText.contains('<')) return false;
 | 
			
		||||
        const auto splitResult = SplitByTags(origText);
 | 
			
		||||
        if (splitResult.empty()) return false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -199,9 +269,111 @@ namespace GakumasLocal::Local {
 | 
			
		|||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ReplaceNumberComma(std::string* orig) {
 | 
			
		||||
        if (!orig->contains(",")) return;
 | 
			
		||||
        std::string newStr = *orig;
 | 
			
		||||
        ReplaceString(&newStr, ",", ",");
 | 
			
		||||
        if (IsPureStringValue(newStr)) {
 | 
			
		||||
            *orig = newStr;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SplitTagsTranslationStat GetSplitTagsTranslationFull(const std::string& origTextIn, std::string* newText, std::vector<std::string>& unTransResultRet) {
 | 
			
		||||
        // static const std::u16string splitFlags = u"0123456789++--%%【】.";
 | 
			
		||||
        static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
 | 
			
		||||
                                                                u'6', u'7', u'8', u'9', u'+', u'+',
 | 
			
		||||
                                                                u'-', u'-', u'%', u'%', u'【', u'】',
 | 
			
		||||
                                                                u'.', u':', u':', u'×'};
 | 
			
		||||
 | 
			
		||||
        const auto origText = Misc::ToUTF16(origTextIn);
 | 
			
		||||
        bool isInTag = false;
 | 
			
		||||
        std::vector<std::string> waitingReplaceTexts{};
 | 
			
		||||
 | 
			
		||||
        std::u16string currentWaitingReplaceText;
 | 
			
		||||
 | 
			
		||||
#ifdef GKMS_WINDOWS
 | 
			
		||||
#define checkCurrentWaitingReplaceTextAndClear() \
 | 
			
		||||
    if (!currentWaitingReplaceText.empty()) { \
 | 
			
		||||
        auto trimmed = trim(Misc::ToUTF8(currentWaitingReplaceText)); \
 | 
			
		||||
        waitingReplaceTexts.push_back(trimmed); \
 | 
			
		||||
        currentWaitingReplaceText.clear(); }
 | 
			
		||||
#else
 | 
			
		||||
#define checkCurrentWaitingReplaceTextAndClear() \
 | 
			
		||||
    if (!currentWaitingReplaceText.empty()) { \
 | 
			
		||||
        waitingReplaceTexts.push_back(Misc::ToUTF8(currentWaitingReplaceText)); \
 | 
			
		||||
        currentWaitingReplaceText.clear(); }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
        for (char16_t currChar : origText) {
 | 
			
		||||
            if (currChar == u'<') {
 | 
			
		||||
                isInTag = true;
 | 
			
		||||
            }
 | 
			
		||||
            if (currChar == u'>') {
 | 
			
		||||
                isInTag = false;
 | 
			
		||||
                checkCurrentWaitingReplaceTextAndClear()
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (isInTag) {
 | 
			
		||||
                checkCurrentWaitingReplaceTextAndClear()
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!splitFlags.contains(currChar)) {
 | 
			
		||||
                currentWaitingReplaceText.push_back(currChar);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                checkCurrentWaitingReplaceTextAndClear()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (waitingReplaceTexts.empty()) {
 | 
			
		||||
            if (currentWaitingReplaceText.empty()) {
 | 
			
		||||
                return SplitTagsTranslationStat::NO_SPLIT_AND_EMPTY;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                if (!(!origText.empty() && splitFlags.contains(origText[0]))) {  // 开头为特殊符号或数字
 | 
			
		||||
                    return SplitTagsTranslationStat::NO_SPLIT;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        checkCurrentWaitingReplaceTextAndClear()
 | 
			
		||||
 | 
			
		||||
        *newText = origTextIn;
 | 
			
		||||
        SplitTagsTranslationStat ret;
 | 
			
		||||
        bool hasTrans = false;
 | 
			
		||||
        bool hasNotTrans = false;
 | 
			
		||||
        if (!waitingReplaceTexts.empty()) {
 | 
			
		||||
            for (const auto& i : waitingReplaceTexts) {
 | 
			
		||||
                std::string searchResult = findInMapIgnoreSpace(i, genericSplitText);
 | 
			
		||||
                if (!searchResult.empty()) {
 | 
			
		||||
                    ReplaceNumberComma(&searchResult);
 | 
			
		||||
                    ReplaceString(newText, i, searchResult);
 | 
			
		||||
                    hasTrans = true;
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    unTransResultRet.emplace_back(trim(i));
 | 
			
		||||
                    hasNotTrans = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (hasTrans && hasNotTrans) {
 | 
			
		||||
                ret = SplitTagsTranslationStat::PART_TRANS;
 | 
			
		||||
            }
 | 
			
		||||
            else if (hasTrans && !hasNotTrans) {
 | 
			
		||||
                ret = SplitTagsTranslationStat::FULL_TRANS;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                ret = SplitTagsTranslationStat::NO_TRANS;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            ret = SplitTagsTranslationStat::NO_TRANS;
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void LoadData() {
 | 
			
		||||
        static auto localizationFile = GetBasePath() / "local-files" / "localization.json";
 | 
			
		||||
        static auto genericFile = GetBasePath() / "local-files" / "generic.json";
 | 
			
		||||
        static auto genericSplitFile = GetBasePath() / "local-files" / "generic.split.json";
 | 
			
		||||
        static auto genericDir = GetBasePath() / "local-files" / "genericTrans";
 | 
			
		||||
 | 
			
		||||
        if (!std::filesystem::is_regular_file(localizationFile)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -211,13 +383,24 @@ namespace GakumasLocal::Local {
 | 
			
		|||
        LoadJsonDataToMap(localizationFile, i18nData, true);
 | 
			
		||||
        Log::InfoFmt("%ld localization items loaded.", i18nData.size());
 | 
			
		||||
 | 
			
		||||
        LoadJsonDataToMap(genericFile, genericText, true);
 | 
			
		||||
        LoadJsonDataToMap(genericFile, genericText, true, true, true);
 | 
			
		||||
        genericSplitText.clear();
 | 
			
		||||
        genericFmtText.clear();
 | 
			
		||||
        LoadJsonDataToMap(genericSplitFile, genericSplitText, true, true, true);
 | 
			
		||||
        if (std::filesystem::exists(genericDir) || std::filesystem::is_directory(genericDir)) {
 | 
			
		||||
            for (const auto& entry : std::filesystem::recursive_directory_iterator(genericDir)) {
 | 
			
		||||
                if (std::filesystem::is_regular_file(entry.path())) {
 | 
			
		||||
                    const auto currFile = entry.path();
 | 
			
		||||
                    const auto& currFile = entry.path();
 | 
			
		||||
                    if (to_lower(currFile.extension().string()) == ".json") {
 | 
			
		||||
                        LoadJsonDataToMap(currFile, genericText, true, false);
 | 
			
		||||
                        if (currFile.filename().string().ends_with(".split.json")) {  // split text file
 | 
			
		||||
                            LoadJsonDataToMap(currFile, genericSplitText, true, false, true);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (currFile.filename().string().ends_with(".fmt.json")) {  // fmt text file
 | 
			
		||||
                            LoadJsonDataToMap(currFile, genericFmtText, true, false, false);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            LoadJsonDataToMap(currFile, genericText, true, false, true);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +457,7 @@ namespace GakumasLocal::Local {
 | 
			
		|||
            const auto targetFilePath = basePath / "local-files" / "resource" / name;
 | 
			
		||||
            // Log::DebugFmt("GetResourceText: %s", targetFilePath.c_str());
 | 
			
		||||
            if (exists(targetFilePath)) {
 | 
			
		||||
                auto readStr = readFileToString(targetFilePath);
 | 
			
		||||
                auto readStr = readFileToString(targetFilePath.string());
 | 
			
		||||
                *ret = readStr;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -285,58 +468,142 @@ namespace GakumasLocal::Local {
 | 
			
		|||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string GetDumpGenericFileName() {
 | 
			
		||||
        if (genericDumpFileIndex == 0) return "generic.json";
 | 
			
		||||
        return Log::StringFormat("generic_%d.json", genericDumpFileIndex);
 | 
			
		||||
    std::string GetDumpGenericFileName(DumpStrStat stat = DumpStrStat::DEFAULT) {
 | 
			
		||||
        if (stat == DumpStrStat::SPLITTABLE_ORIG) {
 | 
			
		||||
            if (genericDumpFileIndex == 0) return "generic_orig.json";
 | 
			
		||||
            return Log::StringFormat("generic_orig_%d.json", genericDumpFileIndex);
 | 
			
		||||
        }
 | 
			
		||||
        else if (stat == DumpStrStat::FMT) {
 | 
			
		||||
            if (genericDumpFileIndex == 0) return "generic.fmt.json";
 | 
			
		||||
            return Log::StringFormat("generic_%d.fmt.json", genericDumpFileIndex);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            if (genericDumpFileIndex == 0) return "generic.json";
 | 
			
		||||
            return Log::StringFormat("generic_%d.json", genericDumpFileIndex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool inDumpGeneric = false;
 | 
			
		||||
    void DumpGenericText(const std::string& origText) {
 | 
			
		||||
    void DumpGenericText(const std::string& origText, DumpStrStat stat = DumpStrStat::DEFAULT) {
 | 
			
		||||
        if (translatedText.contains(origText)) return;
 | 
			
		||||
 | 
			
		||||
        if (std::find(genericTextDumpData.begin(), genericTextDumpData.end(), origText) != genericTextDumpData.end()) {
 | 
			
		||||
        std::array<std::reference_wrapper<std::vector<std::string>>, 4> targets = {
 | 
			
		||||
                genericTextDumpData,
 | 
			
		||||
                genericOrigTextDumpData,
 | 
			
		||||
                genericSplittedDumpData,
 | 
			
		||||
                genericFmtTextDumpData
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        auto& appendTarget = targets[static_cast<int>(stat)].get();
 | 
			
		||||
 | 
			
		||||
        if (std::find(appendTarget.begin(), appendTarget.end(), origText) != appendTarget.end()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (IsPureStringValue(origText)) return;
 | 
			
		||||
 | 
			
		||||
        genericTextDumpData.push_back(origText);
 | 
			
		||||
        appendTarget.push_back(origText);
 | 
			
		||||
        static auto dumpBasePath = GetBasePath() / "dump-files";
 | 
			
		||||
 | 
			
		||||
        if (inDumpGeneric) return;
 | 
			
		||||
        inDumpGeneric = true;
 | 
			
		||||
        std::thread([](){
 | 
			
		||||
            std::this_thread::sleep_for(std::chrono::seconds(5));
 | 
			
		||||
            DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(), genericTextDumpData);
 | 
			
		||||
            DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::DEFAULT), genericTextDumpData);
 | 
			
		||||
            DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTABLE_ORIG), genericOrigTextDumpData);
 | 
			
		||||
            DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTED), genericSplittedDumpData, splitTextPrefix);
 | 
			
		||||
            DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::FMT), genericFmtTextDumpData);
 | 
			
		||||
            genericTextDumpData.clear();
 | 
			
		||||
            genericSplittedDumpData.clear();
 | 
			
		||||
            genericOrigTextDumpData.clear();
 | 
			
		||||
            genericFmtTextDumpData.clear();
 | 
			
		||||
            inDumpGeneric = false;
 | 
			
		||||
        }).detach();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool GetGenericText(const std::string& origText, std::string* newStr) {
 | 
			
		||||
        // 完全匹配
 | 
			
		||||
        if (const auto iter = genericText.find(origText); iter != genericText.end()) {
 | 
			
		||||
            *newStr = iter->second;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::vector<std::string> unTransResultRet;
 | 
			
		||||
        if (GetSplitTagsTranslation(origText, newStr, unTransResultRet)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!Config::dumpText) {
 | 
			
		||||
        // 不翻译翻译过的文本
 | 
			
		||||
        if (translatedText.contains(origText)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (unTransResultRet.empty()) {
 | 
			
		||||
        // 匹配升级卡名
 | 
			
		||||
        if (auto plusPos = origText.find_last_not_of('+'); plusPos != std::string::npos) {
 | 
			
		||||
            const auto noPlusText = origText.substr(0, plusPos + 1);
 | 
			
		||||
 | 
			
		||||
            if (const auto iter = genericText.find(noPlusText); iter != genericText.end()) {
 | 
			
		||||
                size_t plusCount = origText.length() - (plusPos + 1);
 | 
			
		||||
                *newStr = iter->second + std::string(plusCount, '+');
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // fmt 文本
 | 
			
		||||
        auto fmtText = StringParser::ParseItems::parse(origText, false);
 | 
			
		||||
        if (fmtText.isValid) {
 | 
			
		||||
            const auto fmtStr = fmtText.ToFmtString();
 | 
			
		||||
            if (auto it = genericFmtText.find(fmtStr); it != genericFmtText.end()) {
 | 
			
		||||
                auto newRet = fmtText.MergeText(it->second);
 | 
			
		||||
                if (!newRet.empty()) {
 | 
			
		||||
                    *newStr = newRet;
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (Config::dumpText) {
 | 
			
		||||
                DumpGenericText(fmtStr, DumpStrStat::FMT);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto ret = false;
 | 
			
		||||
 | 
			
		||||
        // 分割匹配
 | 
			
		||||
        std::vector<std::string> unTransResultRet;
 | 
			
		||||
        const auto splitTransStat = GetSplitTagsTranslationFull(origText, newStr, unTransResultRet);
 | 
			
		||||
        switch (splitTransStat) {
 | 
			
		||||
            case SplitTagsTranslationStat::FULL_TRANS: {
 | 
			
		||||
                DumpGenericText(origText, DumpStrStat::SPLITTABLE_ORIG);
 | 
			
		||||
                return true;
 | 
			
		||||
            } break;
 | 
			
		||||
 | 
			
		||||
            case SplitTagsTranslationStat::NO_SPLIT_AND_EMPTY: {
 | 
			
		||||
                return false;
 | 
			
		||||
            } break;
 | 
			
		||||
 | 
			
		||||
            case SplitTagsTranslationStat::NO_SPLIT: {
 | 
			
		||||
                ret = false;
 | 
			
		||||
            } break;
 | 
			
		||||
 | 
			
		||||
            case SplitTagsTranslationStat::NO_TRANS: {
 | 
			
		||||
                ret = false;
 | 
			
		||||
            } break;
 | 
			
		||||
 | 
			
		||||
            case SplitTagsTranslationStat::PART_TRANS: {
 | 
			
		||||
                ret = true;
 | 
			
		||||
            } break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!Config::dumpText) {
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (unTransResultRet.empty() || (splitTransStat == SplitTagsTranslationStat::NO_SPLIT)) {
 | 
			
		||||
            DumpGenericText(origText);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            for (const auto& i : unTransResultRet) {
 | 
			
		||||
                DumpGenericText(i);
 | 
			
		||||
                DumpGenericText(i, DumpStrStat::SPLITTED);
 | 
			
		||||
            }
 | 
			
		||||
            // 若未翻译部分长度为1,且未翻译文本等于原文本,则不 dump 到原文本文件
 | 
			
		||||
            //if (unTransResultRet.size() != 1 || unTransResultRet[0] != origText) {
 | 
			
		||||
                DumpGenericText(origText, DumpStrStat::SPLITTABLE_ORIG);
 | 
			
		||||
            //}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string ChangeDumpTextIndex(int changeValue) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,11 @@
 | 
			
		|||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::Local {
 | 
			
		||||
    extern std::unordered_set<std::string> translatedText;
 | 
			
		||||
 | 
			
		||||
    std::filesystem::path GetBasePath();
 | 
			
		||||
    void LoadData();
 | 
			
		||||
    bool GetI18n(const std::string& key, std::string* ret);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,19 @@
 | 
			
		|||
#include "Log.h"
 | 
			
		||||
#include <android/log.h>
 | 
			
		||||
#include <Misc.hpp>
 | 
			
		||||
#include "Misc.hpp"
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <queue>
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
    #include <android/log.h>
 | 
			
		||||
 | 
			
		||||
    extern JavaVM* g_javaVM;
 | 
			
		||||
    extern jclass g_gakumasHookMainClass;
 | 
			
		||||
    extern jmethodID showToastMethodId;
 | 
			
		||||
#endif // GKMS_WINDOWS
 | 
			
		||||
 | 
			
		||||
extern JavaVM* g_javaVM;
 | 
			
		||||
extern jclass g_gakumasHookMainClass;
 | 
			
		||||
extern jmethodID showToastMethodId;
 | 
			
		||||
 | 
			
		||||
#define GetParamStringResult(name)\
 | 
			
		||||
    va_list args;\
 | 
			
		||||
| 
						 | 
				
			
			@ -24,9 +30,13 @@ extern jmethodID showToastMethodId;
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::Log {
 | 
			
		||||
    namespace {
 | 
			
		||||
        std::queue<std::string> showingToasts{};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string StringFormat(const char* fmt, ...) {
 | 
			
		||||
        GetParamStringResult(result);
 | 
			
		||||
        return result.c_str();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Log(int prio, const char* msg) {
 | 
			
		||||
| 
						 | 
				
			
			@ -70,38 +80,8 @@ namespace GakumasLocal::Log {
 | 
			
		|||
        __android_log_write(prio, "GakumasLog", result.c_str());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShowToast(const std::string& text) {
 | 
			
		||||
        DebugFmt("Toast: %s", text.c_str());
 | 
			
		||||
 | 
			
		||||
        std::thread([text](){
 | 
			
		||||
            auto env = Misc::GetJNIEnv();
 | 
			
		||||
            if (!env) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            jclass& kotlinClass = g_gakumasHookMainClass;
 | 
			
		||||
            if (!kotlinClass) {
 | 
			
		||||
                g_javaVM->DetachCurrentThread();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            jmethodID& methodId = showToastMethodId;
 | 
			
		||||
            if (!methodId) {
 | 
			
		||||
                g_javaVM->DetachCurrentThread();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            jstring param = env->NewStringUTF(text.c_str());
 | 
			
		||||
            env->CallStaticVoidMethod(kotlinClass, methodId, param);
 | 
			
		||||
 | 
			
		||||
            g_javaVM->DetachCurrentThread();
 | 
			
		||||
        }).detach();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShowToastFmt(const char* fmt, ...) {
 | 
			
		||||
        GetParamStringResult(result);
 | 
			
		||||
        ShowToast(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShowToast(const char* text) {
 | 
			
		||||
    /*
 | 
			
		||||
    void ShowToastJNI(const char* text) {
 | 
			
		||||
        DebugFmt("Toast: %s", text);
 | 
			
		||||
 | 
			
		||||
        std::thread([text](){
 | 
			
		||||
| 
						 | 
				
			
			@ -125,5 +105,52 @@ namespace GakumasLocal::Log {
 | 
			
		|||
 | 
			
		||||
            g_javaVM->DetachCurrentThread();
 | 
			
		||||
        }).detach();
 | 
			
		||||
    }*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    void ShowToast(const std::string& text) {
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
        showingToasts.push(text);
 | 
			
		||||
#else
 | 
			
		||||
		InfoFmt("Toast: %s", text.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShowToast(const char* text) {
 | 
			
		||||
        // DebugFmt("Toast: %s", text);
 | 
			
		||||
        return ShowToast(std::string(text));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShowToastFmt(const char* fmt, ...) {
 | 
			
		||||
        GetParamStringResult(result);
 | 
			
		||||
        ShowToast(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string GetQueuedToast() {
 | 
			
		||||
        if (showingToasts.empty()) {
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
        const auto ret = showingToasts.front();
 | 
			
		||||
        showingToasts.pop();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
    void ToastLoop(JNIEnv *env, jclass clazz) {
 | 
			
		||||
        const auto toastString = GetQueuedToast();
 | 
			
		||||
        if (toastString.empty()) return;
 | 
			
		||||
 | 
			
		||||
        static auto _showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
 | 
			
		||||
 | 
			
		||||
        if (env && clazz && _showToastMethodId) {
 | 
			
		||||
            jstring param = env->NewStringUTF(toastString.c_str());
 | 
			
		||||
            env->CallStaticVoidMethod(clazz, _showToastMethodId, param);
 | 
			
		||||
            env->DeleteLocalRef(param);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            _showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,15 @@
 | 
			
		|||
#ifndef GAKUMAS_LOCALIFY_LOG_H
 | 
			
		||||
#define GAKUMAS_LOCALIFY_LOG_H
 | 
			
		||||
 | 
			
		||||
#include "../platformDefine.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
    #include <jni.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::Log {
 | 
			
		||||
    std::string StringFormat(const char* fmt, ...);
 | 
			
		||||
    void LogUnityLog(int prio, const char* fmt, ...);
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +23,10 @@ namespace GakumasLocal::Log {
 | 
			
		|||
 | 
			
		||||
    void ShowToast(const char* text);
 | 
			
		||||
    void ShowToastFmt(const char* fmt, ...);
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
    void ToastLoop(JNIEnv *env, jclass clazz);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //GAKUMAS_LOCALIFY_LOG_H
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,809 @@
 | 
			
		|||
#include "MasterLocal.h"
 | 
			
		||||
#include "Local.h"
 | 
			
		||||
#include "Il2cppUtils.hpp"
 | 
			
		||||
#include "config/Config.hpp"
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <regex>
 | 
			
		||||
#include <nlohmann/json.hpp>
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::MasterLocal {
 | 
			
		||||
    using Il2cppString = UnityResolve::UnityType::String;
 | 
			
		||||
 | 
			
		||||
    static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldSetCache;
 | 
			
		||||
    static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldGetCache;
 | 
			
		||||
 | 
			
		||||
    enum class JsonValueType {
 | 
			
		||||
        JVT_String,
 | 
			
		||||
        JVT_Int,
 | 
			
		||||
        JVT_Object,
 | 
			
		||||
        JVT_ArrayObject,
 | 
			
		||||
        JVT_ArrayString,
 | 
			
		||||
        JVT_Unsupported,
 | 
			
		||||
        JVT_NeedMore_EmptyArray
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct ItemRule {
 | 
			
		||||
        std::vector<std::string> mainPrimaryKey;
 | 
			
		||||
        std::map<std::string, std::vector<std::string>> subPrimaryKey;
 | 
			
		||||
 | 
			
		||||
        std::vector<std::string> mainLocalKey;
 | 
			
		||||
        std::map<std::string, std::vector<std::string>> subLocalKey;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct TableLocalData {
 | 
			
		||||
        ItemRule itemRule;
 | 
			
		||||
 | 
			
		||||
        std::unordered_map<std::string, JsonValueType> mainKeyType;
 | 
			
		||||
        std::unordered_map<std::string, std::unordered_map<std::string, JsonValueType>> subKeyType;
 | 
			
		||||
 | 
			
		||||
        std::unordered_map<std::string, std::string> transData;
 | 
			
		||||
        std::unordered_map<std::string, std::vector<std::string>> transStrListData;
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] JsonValueType GetMainKeyType(const std::string& mainKey) const {
 | 
			
		||||
            if (auto it = mainKeyType.find(mainKey); it != mainKeyType.end()) {
 | 
			
		||||
                return it->second;
 | 
			
		||||
            }
 | 
			
		||||
            return JsonValueType::JVT_Unsupported;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] JsonValueType GetSubKeyType(const std::string& parentKey, const std::string& subKey) const {
 | 
			
		||||
            if (auto it = subKeyType.find(parentKey); it != subKeyType.end()) {
 | 
			
		||||
                if (auto subIt = it->second.find(subKey); subIt != it->second.end()) {
 | 
			
		||||
                    return subIt->second;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return JsonValueType::JVT_Unsupported;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static std::unordered_map<std::string, TableLocalData> masterLocalData;
 | 
			
		||||
 | 
			
		||||
    class FieldController {
 | 
			
		||||
        void* self;
 | 
			
		||||
        std::string self_klass_name;
 | 
			
		||||
 | 
			
		||||
        static std::string capitalizeFirstLetter(const std::string& input) {
 | 
			
		||||
            if (input.empty()) return input;
 | 
			
		||||
            std::string result = input;
 | 
			
		||||
            result[0] = static_cast<char>(std::toupper(result[0]));
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Il2cppUtils::MethodInfo* GetGetSetMethodFromCache(const std::string& fieldName, int argsCount,
 | 
			
		||||
                                                          std::unordered_map<std::string, Il2cppUtils::MethodInfo*>& fromCache, const std::string& prefix = "set_") {
 | 
			
		||||
            const std::string methodName = prefix + capitalizeFirstLetter(fieldName);
 | 
			
		||||
            const std::string searchName = self_klass_name + "." + methodName;
 | 
			
		||||
 | 
			
		||||
            if (auto it = fromCache.find(searchName); it != fromCache.end()) {
 | 
			
		||||
                return it->second;
 | 
			
		||||
            }
 | 
			
		||||
            auto set_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
 | 
			
		||||
                    self_klass,
 | 
			
		||||
                    methodName.c_str(),
 | 
			
		||||
                    argsCount
 | 
			
		||||
            );
 | 
			
		||||
            fromCache.emplace(searchName, set_mtd);
 | 
			
		||||
            return set_mtd;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        Il2cppUtils::Il2CppClassHead* self_klass;
 | 
			
		||||
 | 
			
		||||
        explicit FieldController(void* from) {
 | 
			
		||||
            if (!from) {
 | 
			
		||||
                self = nullptr;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            self = from;
 | 
			
		||||
            self_klass = Il2cppUtils::get_class_from_instance(self);
 | 
			
		||||
            if (self_klass) {
 | 
			
		||||
                self_klass_name = self_klass->name;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        template<typename T>
 | 
			
		||||
        T ReadField(const std::string& fieldName) {
 | 
			
		||||
            if (!self) return T();
 | 
			
		||||
            auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
 | 
			
		||||
            if (get_mtd) {
 | 
			
		||||
                return reinterpret_cast<T (*)(void*, void*)>(get_mtd->methodPointer)(self, get_mtd);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
 | 
			
		||||
                    "il2cpp_class_get_field_from_name",
 | 
			
		||||
                    self_klass,
 | 
			
		||||
                    (fieldName + '_').c_str()
 | 
			
		||||
            );
 | 
			
		||||
            if (!field) {
 | 
			
		||||
                return T();
 | 
			
		||||
            }
 | 
			
		||||
            return Il2cppUtils::ClassGetFieldValue<T>(self, field);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        template<typename T>
 | 
			
		||||
        void SetField(const std::string& fieldName, T value) {
 | 
			
		||||
            if (!self) return;
 | 
			
		||||
            auto set_mtd = GetGetSetMethodFromCache(fieldName, 1, fieldSetCache, "set_");
 | 
			
		||||
            if (set_mtd) {
 | 
			
		||||
                reinterpret_cast<void (*)(void*, T, void*)>(
 | 
			
		||||
                        set_mtd->methodPointer
 | 
			
		||||
                )(self, value, set_mtd);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
 | 
			
		||||
                    "il2cpp_class_get_field_from_name",
 | 
			
		||||
                    self_klass,
 | 
			
		||||
                    (fieldName + '_').c_str()
 | 
			
		||||
            );
 | 
			
		||||
            if (!field) return;
 | 
			
		||||
            Il2cppUtils::ClassSetFieldValue(self, field, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int ReadIntField(const std::string& fieldName) {
 | 
			
		||||
            return ReadField<int>(fieldName);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Il2cppString* ReadStringField(const std::string& fieldName) {
 | 
			
		||||
            if (!self) return nullptr;
 | 
			
		||||
            auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
 | 
			
		||||
            if (!get_mtd) {
 | 
			
		||||
                return ReadField<Il2cppString*>(fieldName);
 | 
			
		||||
            }
 | 
			
		||||
            auto returnClass = UnityResolve::Invoke<Il2cppUtils::Il2CppClassHead*>(
 | 
			
		||||
                    "il2cpp_class_from_type",
 | 
			
		||||
                    UnityResolve::Invoke<void*>("il2cpp_method_get_return_type", get_mtd)
 | 
			
		||||
            );
 | 
			
		||||
            if (!returnClass) {
 | 
			
		||||
                return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
 | 
			
		||||
                        get_mtd->methodPointer
 | 
			
		||||
                )(self, get_mtd);
 | 
			
		||||
            }
 | 
			
		||||
            auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", returnClass);
 | 
			
		||||
            if (!isEnum) {
 | 
			
		||||
                return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
 | 
			
		||||
                        get_mtd->methodPointer
 | 
			
		||||
                )(self, get_mtd);
 | 
			
		||||
            }
 | 
			
		||||
            auto enumMap = Il2cppUtils::EnumToValueMap(returnClass, true);
 | 
			
		||||
            auto enumValue = reinterpret_cast<int (*)(void*, void*)>(
 | 
			
		||||
                    get_mtd->methodPointer
 | 
			
		||||
            )(self, get_mtd);
 | 
			
		||||
            if (auto it = enumMap.find(enumValue); it != enumMap.end()) {
 | 
			
		||||
                return Il2cppString::New(it->second);
 | 
			
		||||
            }
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void SetStringField(const std::string& fieldName, const std::string& value) {
 | 
			
		||||
            if (!self) return;
 | 
			
		||||
            auto newString = Il2cppString::New(value);
 | 
			
		||||
            SetField(fieldName, newString);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void SetStringListField(const std::string& fieldName, const std::vector<std::string>& data) {
 | 
			
		||||
            if (!self) return;
 | 
			
		||||
            static auto List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str(
 | 
			
		||||
                    "System.Collections.Generic.List`1[System.String]"
 | 
			
		||||
            );
 | 
			
		||||
            static auto List_String_ctor_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
 | 
			
		||||
                    List_String_klass, ".ctor", 0
 | 
			
		||||
            );
 | 
			
		||||
            static auto List_String_ctor = reinterpret_cast<void (*)(void*, void*)>(
 | 
			
		||||
                    List_String_ctor_mtd->methodPointer
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            auto newList = UnityResolve::Invoke<void*>("il2cpp_object_new", List_String_klass);
 | 
			
		||||
            List_String_ctor(newList, List_String_ctor_mtd);
 | 
			
		||||
 | 
			
		||||
            Il2cppUtils::Tools::CSListEditor<Il2cppString*> newListEditor(newList);
 | 
			
		||||
            for (auto& s : data) {
 | 
			
		||||
                newListEditor.Add(Il2cppString::New(s));
 | 
			
		||||
            }
 | 
			
		||||
            SetField(fieldName, newList);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void* ReadObjectField(const std::string& fieldName) {
 | 
			
		||||
            if (!self) return nullptr;
 | 
			
		||||
            return ReadField<void*>(fieldName);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void* ReadObjectListField(const std::string& fieldName) {
 | 
			
		||||
            if (!self) return nullptr;
 | 
			
		||||
            return ReadField<void*>(fieldName);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static FieldController CreateSubFieldController(void* subObj) {
 | 
			
		||||
            return FieldController(subObj);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FieldController CreateSubFieldController(const std::string& subObjName) {
 | 
			
		||||
            auto field = ReadObjectField(subObjName);
 | 
			
		||||
            return FieldController(field);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    JsonValueType checkJsonValueType(const nlohmann::json& j) {
 | 
			
		||||
        if (j.is_string())  return JsonValueType::JVT_String;
 | 
			
		||||
        if (j.is_number_integer()) return JsonValueType::JVT_Int;
 | 
			
		||||
        if (j.is_object())  return JsonValueType::JVT_Object;
 | 
			
		||||
        if (j.is_array()) {
 | 
			
		||||
            if (!j.empty()) {
 | 
			
		||||
                if (j.begin()->is_object()) {
 | 
			
		||||
                    return JsonValueType::JVT_ArrayObject;
 | 
			
		||||
                }
 | 
			
		||||
                else if (j.begin()->is_string()) {
 | 
			
		||||
                    return JsonValueType::JVT_ArrayString;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                return JsonValueType::JVT_NeedMore_EmptyArray;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return JsonValueType::JVT_Unsupported;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    std::string ReadFileToString(const std::filesystem::path& path) {
 | 
			
		||||
        std::ifstream ifs(path, std::ios::binary);
 | 
			
		||||
        if (!ifs) return {};
 | 
			
		||||
        std::stringstream buffer;
 | 
			
		||||
        buffer << ifs.rdbuf();
 | 
			
		||||
        return buffer.str();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    namespace Load {
 | 
			
		||||
        std::vector<std::string> ArrayStrJsonToVec(nlohmann::json& data) {
 | 
			
		||||
            return data;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool BuildObjectItemLocalRule(nlohmann::json& transData, ItemRule& itemRule) {
 | 
			
		||||
            // transData: data[]
 | 
			
		||||
            bool hasSuccess = false;
 | 
			
		||||
            for (auto& data : transData) {
 | 
			
		||||
                // data: {"id": "xxx", "produceDescriptions": [{"k", "v"}], "descriptions": {"k2", "v2"}}
 | 
			
		||||
                if (!data.is_object()) continue;
 | 
			
		||||
                for (auto& [key, value] : data.items()) {
 | 
			
		||||
                    // key: "id", value: "xxx"
 | 
			
		||||
                    // key: "produceDescriptions", value: [{"k", "v"}]
 | 
			
		||||
                    const auto valueType = checkJsonValueType(value);
 | 
			
		||||
                    switch (valueType) {
 | 
			
		||||
                        case JsonValueType::JVT_String:
 | 
			
		||||
                            // case JsonValueType::JVT_Int:
 | 
			
		||||
                        case JsonValueType::JVT_ArrayString: {
 | 
			
		||||
                            if (std::find(itemRule.mainPrimaryKey.begin(), itemRule.mainPrimaryKey.end(), key) != itemRule.mainPrimaryKey.end()) {
 | 
			
		||||
                                continue;
 | 
			
		||||
                            }
 | 
			
		||||
                            if (auto it = std::find(itemRule.mainLocalKey.begin(), itemRule.mainLocalKey.end(), key); it == itemRule.mainLocalKey.end()) {
 | 
			
		||||
                                itemRule.mainLocalKey.emplace_back(key);
 | 
			
		||||
                            }
 | 
			
		||||
                            hasSuccess = true;
 | 
			
		||||
                        } break;
 | 
			
		||||
 | 
			
		||||
                        case JsonValueType::JVT_Object: {
 | 
			
		||||
                            ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
 | 
			
		||||
 | 
			
		||||
                            auto vJson = nlohmann::json::array();
 | 
			
		||||
                            vJson.push_back(value);
 | 
			
		||||
 | 
			
		||||
                            if (BuildObjectItemLocalRule(vJson, currRule)) {
 | 
			
		||||
                                itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
 | 
			
		||||
                                hasSuccess = true;
 | 
			
		||||
                            }
 | 
			
		||||
                        } break;
 | 
			
		||||
 | 
			
		||||
                        case JsonValueType::JVT_ArrayObject: {
 | 
			
		||||
                            for (auto& obj : value) {
 | 
			
		||||
                                // obj: {"k", "v"}
 | 
			
		||||
                                ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
 | 
			
		||||
                                if (BuildObjectItemLocalRule(value, currRule)) {
 | 
			
		||||
                                    itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
 | 
			
		||||
                                    hasSuccess = true;
 | 
			
		||||
                                    break;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } break;
 | 
			
		||||
 | 
			
		||||
                        case JsonValueType::JVT_Unsupported:
 | 
			
		||||
                        default:
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (hasSuccess) break;
 | 
			
		||||
            }
 | 
			
		||||
            return hasSuccess;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool GetItemRule(nlohmann::json& fullData, ItemRule& itemRule) {
 | 
			
		||||
            auto& primaryKeys = fullData["rules"]["primaryKeys"];
 | 
			
		||||
            auto& transData = fullData["data"];
 | 
			
		||||
            if (!primaryKeys.is_array()) return false;
 | 
			
		||||
            if (!transData.is_array()) return false;
 | 
			
		||||
 | 
			
		||||
            // 首先构造 mainPrimaryKey 规则
 | 
			
		||||
            for (auto& pkItem : primaryKeys) {
 | 
			
		||||
                if (!pkItem.is_string()) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                std::string pk = pkItem;
 | 
			
		||||
                auto dotCount = std::ranges::count(pk, '.');
 | 
			
		||||
                if (dotCount == 0) {
 | 
			
		||||
                    itemRule.mainPrimaryKey.emplace_back(pk);
 | 
			
		||||
                }
 | 
			
		||||
                else if (dotCount == 1) {
 | 
			
		||||
                    auto [parentKey, subKey] = Misc::StringFormat::split_once(pk, ".");
 | 
			
		||||
                    if (itemRule.subPrimaryKey.contains(parentKey)) {
 | 
			
		||||
                        itemRule.subPrimaryKey[parentKey].emplace_back(subKey);
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        itemRule.subPrimaryKey.emplace(parentKey, std::vector<std::string>{subKey});
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    Log::ErrorFmt("Unsupported depth: %d", dotCount);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return BuildObjectItemLocalRule(transData, itemRule);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string BuildBaseMainUniqueKey(nlohmann::json& data, TableLocalData& tableLocalData) {
 | 
			
		||||
            try {
 | 
			
		||||
                std::string mainBaseUniqueKey;
 | 
			
		||||
                for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
 | 
			
		||||
                    if (!data.contains(mainPrimaryKey)) {
 | 
			
		||||
                        return "";
 | 
			
		||||
                    }
 | 
			
		||||
                    auto& value = data[mainPrimaryKey];
 | 
			
		||||
                    if (value.is_number_integer()) {
 | 
			
		||||
                        mainBaseUniqueKey.append(std::to_string(value.get<int>()));
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        mainBaseUniqueKey.append(value);
 | 
			
		||||
                    }
 | 
			
		||||
                    mainBaseUniqueKey.push_back('|');
 | 
			
		||||
                }
 | 
			
		||||
                return mainBaseUniqueKey;
 | 
			
		||||
            }
 | 
			
		||||
            catch (std::exception& e) {
 | 
			
		||||
                Log::ErrorFmt("LoadData - BuildBaseMainUniqueKey failed: %s", e.what());
 | 
			
		||||
                throw e;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void BuildBaseObjectSubUniqueKey(nlohmann::json& value, JsonValueType valueType, std::string& currLocalKey) {
 | 
			
		||||
            switch (valueType) {
 | 
			
		||||
                case JsonValueType::JVT_String:
 | 
			
		||||
                    currLocalKey.append(value.get<std::string>());  // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
 | 
			
		||||
                    currLocalKey.push_back('|');
 | 
			
		||||
                    break;
 | 
			
		||||
                case JsonValueType::JVT_Int:
 | 
			
		||||
                    currLocalKey.append(std::to_string(value.get<int>()));
 | 
			
		||||
                    currLocalKey.push_back('|');
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool BuildUniqueKeyValue(nlohmann::json& data, TableLocalData& tableLocalData) {
 | 
			
		||||
            // 首先处理 main 部分
 | 
			
		||||
            const std::string mainBaseUniqueKey = BuildBaseMainUniqueKey(data, tableLocalData);  // p_card-00-acc-0_002|0|
 | 
			
		||||
            if (mainBaseUniqueKey.empty()) return false;
 | 
			
		||||
            for (auto& mainLocalKey : tableLocalData.itemRule.mainLocalKey) {
 | 
			
		||||
                if (!data.contains(mainLocalKey)) continue;
 | 
			
		||||
                auto& currLocalValue = data[mainLocalKey];
 | 
			
		||||
                auto currUniqueKey = mainBaseUniqueKey + mainLocalKey;  // p_card-00-acc-0_002|0|name
 | 
			
		||||
                if (tableLocalData.GetMainKeyType(mainLocalKey) == JsonValueType::JVT_ArrayString) {
 | 
			
		||||
                    tableLocalData.transStrListData.emplace(currUniqueKey, ArrayStrJsonToVec(currLocalValue));
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    tableLocalData.transData.emplace(currUniqueKey, currLocalValue);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // 然后处理 sub 部分
 | 
			
		||||
            /*
 | 
			
		||||
            for (const auto& [subPrimaryParentKey, subPrimarySubKeys] : tableLocalData.itemRule.subPrimaryKey) {
 | 
			
		||||
                if (!data.contains(subPrimaryParentKey)) continue;
 | 
			
		||||
 | 
			
		||||
                const std::string subBaseUniqueKey = mainBaseUniqueKey + subPrimaryParentKey + '|';  // p_card-00-acc-0_002|0|produceDescriptions|
 | 
			
		||||
 | 
			
		||||
                auto subValueType = checkJsonValueType(data[subPrimaryParentKey]);
 | 
			
		||||
                std::string currLocalKey = subBaseUniqueKey;  // p_card-00-acc-0_002|0|produceDescriptions|
 | 
			
		||||
                switch (subValueType) {
 | 
			
		||||
                    case JsonValueType::JVT_Object: {
 | 
			
		||||
                        for (auto& subPrimarySubKey : subPrimarySubKeys) {
 | 
			
		||||
                            if (!data[subPrimaryParentKey].contains(subPrimarySubKey)) continue;
 | 
			
		||||
                            auto& value = data[subPrimaryParentKey][subPrimarySubKey];
 | 
			
		||||
                            auto valueType = tableLocalData.GetSubKeyType(subPrimaryParentKey, subPrimarySubKey);
 | 
			
		||||
                            BuildBaseObjectSubUniqueKey(value, valueType, currLocalKey);  // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
 | 
			
		||||
                        }
 | 
			
		||||
                    } break;
 | 
			
		||||
                    case JsonValueType::JVT_ArrayObject: {
 | 
			
		||||
                        int currIndex = 0;
 | 
			
		||||
                        for (auto& obj : data[subPrimaryParentKey]) {
 | 
			
		||||
                            for (auto& subPrimarySubKey : subPrimarySubKeys) {
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
                            currIndex++;
 | 
			
		||||
                        }
 | 
			
		||||
                    } break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }*/
 | 
			
		||||
 | 
			
		||||
            for (const auto& [subLocalParentKey, subLocalSubKeys] : tableLocalData.itemRule.subLocalKey) {
 | 
			
		||||
                if (!data.contains(subLocalParentKey)) continue;
 | 
			
		||||
 | 
			
		||||
                const std::string subBaseUniqueKey = mainBaseUniqueKey + subLocalParentKey + '|';  // p_card-00-acc-0_002|0|produceDescriptions|
 | 
			
		||||
                auto subValueType = checkJsonValueType(data[subLocalParentKey]);
 | 
			
		||||
                if (subValueType != JsonValueType::JVT_NeedMore_EmptyArray) {
 | 
			
		||||
                    tableLocalData.mainKeyType.emplace(subLocalParentKey, subValueType);  // 在这里插入 subParent 的类型
 | 
			
		||||
                }
 | 
			
		||||
                switch (subValueType) {
 | 
			
		||||
                    case JsonValueType::JVT_Object: {
 | 
			
		||||
                        for (auto& localSubKey : subLocalSubKeys) {
 | 
			
		||||
                            const std::string currLocalUniqueKey = subBaseUniqueKey + localSubKey;  // p_card-00-acc-0_002|0|produceDescriptions|text
 | 
			
		||||
                            if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
 | 
			
		||||
                                tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(data[subLocalParentKey][localSubKey]));
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                tableLocalData.transData.emplace(currLocalUniqueKey, data[subLocalParentKey][localSubKey]);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } break;
 | 
			
		||||
                    case JsonValueType::JVT_ArrayObject: {
 | 
			
		||||
                        int currIndex = 0;
 | 
			
		||||
                        for (auto& obj : data[subLocalParentKey]) {
 | 
			
		||||
                            for (auto& localSubKey : subLocalSubKeys) {
 | 
			
		||||
                                std::string currLocalUniqueKey = subBaseUniqueKey;  // p_card-00-acc-0_002|0|produceDescriptions|
 | 
			
		||||
                                currLocalUniqueKey.push_back('[');
 | 
			
		||||
                                currLocalUniqueKey.append(std::to_string(currIndex));
 | 
			
		||||
                                currLocalUniqueKey.append("]|");
 | 
			
		||||
                                currLocalUniqueKey.append(localSubKey);  // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
 | 
			
		||||
 | 
			
		||||
                                if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
 | 
			
		||||
                                    // if (obj[localSubKey].is_array()) {
 | 
			
		||||
                                    tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(obj[localSubKey]));
 | 
			
		||||
                                }
 | 
			
		||||
                                else if (obj[localSubKey].is_string()) {
 | 
			
		||||
                                    tableLocalData.transData.emplace(currLocalUniqueKey, obj[localSubKey]);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            currIndex++;
 | 
			
		||||
                        }
 | 
			
		||||
                    } break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
#define MainKeyTypeProcess() if (!data.contains(mainPrimaryKey)) { Log::ErrorFmt("mainPrimaryKey: %s not found", mainPrimaryKey.c_str()); isFailed = true; break; } \
 | 
			
		||||
    auto currType = checkJsonValueType(data[mainPrimaryKey]); \
 | 
			
		||||
    if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
 | 
			
		||||
    tableLocalData.mainKeyType[mainPrimaryKey] = currType
 | 
			
		||||
#define SubKeyTypeProcess() if (!data.contains(subKeyParent)) { Log::ErrorFmt("subKeyParent: %s not found", subKeyParent.c_str()); isFailed = true; break; } \
 | 
			
		||||
                for (auto& subKey : subKeys) { \
 | 
			
		||||
                    auto& subKeyValue = data[subKeyParent]; \
 | 
			
		||||
                    if (subKeyValue.is_object()) { \
 | 
			
		||||
                        if (!subKeyValue.contains(subKey)) { \
 | 
			
		||||
                            Log::ErrorFmt("subKey: %s not in subKeyParent: %s", subKey.c_str(), subKeyParent.c_str()); isFailed = true; break; \
 | 
			
		||||
                        }                                                                                                                                    \
 | 
			
		||||
                        auto currType = checkJsonValueType(subKeyValue[subKey]);                                                                             \
 | 
			
		||||
                        if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
 | 
			
		||||
                        tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
 | 
			
		||||
                    } \
 | 
			
		||||
                    else if (subKeyValue.is_array()) {                                                                                                       \
 | 
			
		||||
                        if (subKeyValue.empty()) goto NextLoop;                                                                                              \
 | 
			
		||||
                        for (auto& i : subKeyValue) { \
 | 
			
		||||
                            if (!i.is_object()) continue; \
 | 
			
		||||
                            if (!i.contains(subKey)) continue;  \
 | 
			
		||||
                            auto currType = checkJsonValueType(i[subKey]); \
 | 
			
		||||
                            if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
 | 
			
		||||
                            tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
 | 
			
		||||
                            break; \
 | 
			
		||||
                        } \
 | 
			
		||||
                    }                                                                                                                                        \
 | 
			
		||||
                    else {                                                                                                                                   \
 | 
			
		||||
                        goto NextLoop;\
 | 
			
		||||
                    } \
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        bool GetTableLocalData(nlohmann::json& fullData, TableLocalData& tableLocalData) {
 | 
			
		||||
            bool isFailed = false;
 | 
			
		||||
 | 
			
		||||
            // 首先 Build mainKeyType 和 subKeyType
 | 
			
		||||
            for (auto& data : fullData["data"]) {
 | 
			
		||||
                if (!data.is_object()) continue;
 | 
			
		||||
 | 
			
		||||
                for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
 | 
			
		||||
                    MainKeyTypeProcess();
 | 
			
		||||
                }
 | 
			
		||||
                for (auto& mainPrimaryKey : tableLocalData.itemRule.mainLocalKey) {
 | 
			
		||||
                    MainKeyTypeProcess();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subPrimaryKey) {
 | 
			
		||||
                    SubKeyTypeProcess()
 | 
			
		||||
 | 
			
		||||
                    if (isFailed) break;
 | 
			
		||||
                }
 | 
			
		||||
                for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subLocalKey) {
 | 
			
		||||
                    SubKeyTypeProcess()
 | 
			
		||||
                    if (isFailed) break;
 | 
			
		||||
                }
 | 
			
		||||
                if (!isFailed) break;
 | 
			
		||||
            NextLoop:
 | 
			
		||||
                ;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (isFailed) return false;
 | 
			
		||||
 | 
			
		||||
            bool hasSuccess = false;
 | 
			
		||||
            // 然后构造 transData
 | 
			
		||||
            for (auto& data : fullData["data"]) {
 | 
			
		||||
                if (!data.is_object()) continue;
 | 
			
		||||
                if (BuildUniqueKeyValue(data, tableLocalData)) {
 | 
			
		||||
                    hasSuccess = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!hasSuccess) {
 | 
			
		||||
                Log::ErrorFmt("BuildUniqueKeyValue failed.");
 | 
			
		||||
            }
 | 
			
		||||
            return hasSuccess;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void LoadData() {
 | 
			
		||||
            masterLocalData.clear();
 | 
			
		||||
            static auto masterDir = Local::GetBasePath() / "local-files" / "masterTrans";
 | 
			
		||||
            if (!std::filesystem::is_directory(masterDir)) {
 | 
			
		||||
                Log::ErrorFmt("LoadData: not found: %s", masterDir.string().c_str());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool isFirstIteration = true;
 | 
			
		||||
            for (auto& p : std::filesystem::directory_iterator(masterDir)) {
 | 
			
		||||
                if (isFirstIteration) {
 | 
			
		||||
                    auto totalFileCount = std::distance(
 | 
			
		||||
                            std::filesystem::directory_iterator(masterDir),
 | 
			
		||||
                            std::filesystem::directory_iterator{}
 | 
			
		||||
                    );
 | 
			
		||||
                    UnityResolveProgress::classProgress.total = totalFileCount <= 0 ? 1 : totalFileCount;
 | 
			
		||||
                    isFirstIteration = false;
 | 
			
		||||
                }
 | 
			
		||||
                UnityResolveProgress::classProgress.current++;
 | 
			
		||||
 | 
			
		||||
                if (!p.is_regular_file()) continue;
 | 
			
		||||
                const auto& path = p.path();
 | 
			
		||||
                if (path.extension() != ".json") continue;
 | 
			
		||||
 | 
			
		||||
                std::string tableName = path.stem().string();
 | 
			
		||||
                auto fileContent = ReadFileToString(path);
 | 
			
		||||
                if (fileContent.empty()) continue;
 | 
			
		||||
 | 
			
		||||
                try {
 | 
			
		||||
                    auto j = nlohmann::json::parse(fileContent);
 | 
			
		||||
                    if (!j.contains("rules") || !j["rules"].contains("primaryKeys")) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    ItemRule currRule;
 | 
			
		||||
                    if (!GetItemRule(j, currRule)) {
 | 
			
		||||
                        Log::ErrorFmt("GetItemRule failed: %s", path.string().c_str());
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    /*
 | 
			
		||||
                    if (tableName == "ProduceStepEventDetail") {
 | 
			
		||||
                        for (auto& i : currRule.mainLocalKey) {
 | 
			
		||||
                            Log::DebugFmt("currRule.mainLocalKey: %s", i.c_str());
 | 
			
		||||
                        }
 | 
			
		||||
                        for (auto& i : currRule.mainPrimaryKey) {
 | 
			
		||||
                            Log::DebugFmt("currRule.mainPrimaryKey: %s", i.c_str());
 | 
			
		||||
                        }
 | 
			
		||||
                        for (auto& i : currRule.subLocalKey) {
 | 
			
		||||
                            for (auto& m : i.second) {
 | 
			
		||||
                                Log::DebugFmt("currRule.subLocalKey: %s - %s", i.first.c_str(), m.c_str());
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        for (auto& i : currRule.subPrimaryKey) {
 | 
			
		||||
                            for (auto& m : i.second) {
 | 
			
		||||
                                Log::DebugFmt("currRule.subPrimaryKey: %s - %s", i.first.c_str(), m.c_str());
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }*/
 | 
			
		||||
 | 
			
		||||
                    TableLocalData tableLocalData{ .itemRule = currRule };
 | 
			
		||||
                    if (GetTableLocalData(j, tableLocalData)) {
 | 
			
		||||
                        for (auto& i : tableLocalData.transData) {
 | 
			
		||||
                            // Log::DebugFmt("%s: %s -> %s", tableName.c_str(), i.first.c_str(), i.second.c_str());
 | 
			
		||||
                            Local::translatedText.emplace(i.second);
 | 
			
		||||
                        }
 | 
			
		||||
                        for (auto& i : tableLocalData.transStrListData) {
 | 
			
		||||
                            for (auto& str : i.second) {
 | 
			
		||||
                                // Log::DebugFmt("%s[]: %s -> %s", tableName.c_str(), i.first.c_str(), str.c_str());
 | 
			
		||||
                                Local::translatedText.emplace(str);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        /*
 | 
			
		||||
                        if (tableName == "ProduceStepEventDetail") {
 | 
			
		||||
                            for (auto& i : tableLocalData.mainKeyType) {
 | 
			
		||||
                                Log::DebugFmt("mainKeyType: %s -> %d", i.first.c_str(), i.second);
 | 
			
		||||
                            }
 | 
			
		||||
                            for (auto& i : tableLocalData.subKeyType) {
 | 
			
		||||
                                for (auto& m : i.second) {
 | 
			
		||||
                                    Log::DebugFmt("subKeyType: %s - %s -> %d", i.first.c_str(), m.first.c_str(), m.second);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }*/
 | 
			
		||||
                        // JVT_ArrayString in HelpCategory, ProduceStory, Tutorial
 | 
			
		||||
 | 
			
		||||
                        masterLocalData.emplace(tableName, std::move(tableLocalData));
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        Log::ErrorFmt("GetTableLocalData failed: %s", path.string().c_str());
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (std::exception& e) {
 | 
			
		||||
                    Log::ErrorFmt("MasterLocal::LoadData: parse error in '%s': %s",
 | 
			
		||||
                                  path.string().c_str(), e.what());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void LoadData() {
 | 
			
		||||
        return Load::LoadData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string GetTransString(const std::string& key, const TableLocalData& localData) {
 | 
			
		||||
        if (auto it = localData.transData.find(key); it != localData.transData.end()) {
 | 
			
		||||
            return it->second;
 | 
			
		||||
        }
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<std::string> GetTransArrayString(const std::string& key, const TableLocalData& localData) {
 | 
			
		||||
        if (auto it = localData.transStrListData.find(key); it != localData.transStrListData.end()) {
 | 
			
		||||
            return it->second;
 | 
			
		||||
        }
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void LocalizeMasterItem(FieldController& fc, const std::string& tableName) {
 | 
			
		||||
        auto it = masterLocalData.find(tableName);
 | 
			
		||||
        if (it == masterLocalData.end()) return;
 | 
			
		||||
        const auto& localData = it->second;
 | 
			
		||||
 | 
			
		||||
        // 首先拼 BasePrimaryKey
 | 
			
		||||
        std::string baseDataKey;  // p_card-00-acc-0_002|0|
 | 
			
		||||
        for (auto& mainPk : localData.itemRule.mainPrimaryKey) {
 | 
			
		||||
            auto mainPkType = localData.GetMainKeyType(mainPk);
 | 
			
		||||
            switch (mainPkType) {
 | 
			
		||||
                case JsonValueType::JVT_Int: {
 | 
			
		||||
                    auto readValue = std::to_string(fc.ReadIntField(mainPk));
 | 
			
		||||
                    baseDataKey.append(readValue);
 | 
			
		||||
                    baseDataKey.push_back('|');
 | 
			
		||||
                } break;
 | 
			
		||||
                case JsonValueType::JVT_String: {
 | 
			
		||||
                    auto readValue = fc.ReadStringField(mainPk);
 | 
			
		||||
                    if (!readValue) return;
 | 
			
		||||
                    baseDataKey.append(readValue->ToString());
 | 
			
		||||
                    baseDataKey.push_back('|');
 | 
			
		||||
                } break;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 然后本地化 mainLocal
 | 
			
		||||
        for (auto& mainLocal : localData.itemRule.mainLocalKey) {
 | 
			
		||||
            std::string currSearchKey = baseDataKey;
 | 
			
		||||
            currSearchKey.append(mainLocal);  // p_card-00-acc-0_002|0|name
 | 
			
		||||
            auto localVType = localData.GetMainKeyType(mainLocal);
 | 
			
		||||
            switch (localVType) {
 | 
			
		||||
                case JsonValueType::JVT_String: {
 | 
			
		||||
                    auto localValue = GetTransString(currSearchKey, localData);
 | 
			
		||||
                    if (!localValue.empty()) {
 | 
			
		||||
                        fc.SetStringField(mainLocal, localValue);
 | 
			
		||||
                    }
 | 
			
		||||
                } break;
 | 
			
		||||
                case JsonValueType::JVT_ArrayString: {
 | 
			
		||||
                    auto localValue = GetTransArrayString(currSearchKey, localData);
 | 
			
		||||
                    if (!localValue.empty()) {
 | 
			
		||||
                        fc.SetStringListField(mainLocal, localValue);
 | 
			
		||||
                    }
 | 
			
		||||
                } break;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 处理 sub
 | 
			
		||||
        for (const auto& [subParentKey, subLocalKeys] : localData.itemRule.subLocalKey) {
 | 
			
		||||
            const auto subBaseSearchKey = baseDataKey + subParentKey + '|';  // p_card-00-acc-0_002|0|produceDescriptions|
 | 
			
		||||
 | 
			
		||||
            const auto subParentType = localData.GetMainKeyType(subParentKey);
 | 
			
		||||
            switch (subParentType) {
 | 
			
		||||
                case JsonValueType::JVT_Object: {
 | 
			
		||||
                    auto subParentField = fc.CreateSubFieldController(subParentKey);
 | 
			
		||||
                    for (const auto& subLocalKey : subLocalKeys) {
 | 
			
		||||
                        const auto currSearchKey = subBaseSearchKey + subLocalKey;  // p_card-00-acc-0_002|0|produceDescriptions|text
 | 
			
		||||
                        auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
 | 
			
		||||
                        if (localKeyType == JsonValueType::JVT_String) {
 | 
			
		||||
                            auto setData = GetTransString(currSearchKey, localData);
 | 
			
		||||
                            if (!setData.empty()) {
 | 
			
		||||
                                subParentField.SetStringField(subLocalKey, setData);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (localKeyType == JsonValueType::JVT_ArrayString) {
 | 
			
		||||
                            auto setData = GetTransArrayString(currSearchKey, localData);
 | 
			
		||||
                            if (!setData.empty()) {
 | 
			
		||||
                                subParentField.SetStringListField(subLocalKey, setData);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } break;
 | 
			
		||||
                case JsonValueType::JVT_ArrayObject: {
 | 
			
		||||
                    auto subArrField = fc.ReadObjectListField(subParentKey);
 | 
			
		||||
                    if (!subArrField) continue;
 | 
			
		||||
                    Il2cppUtils::Tools::CSListEditor<void*> subListEdit(subArrField);
 | 
			
		||||
                    auto count = subListEdit.get_Count();
 | 
			
		||||
                    for (int idx = 0; idx < count; idx++) {
 | 
			
		||||
                        auto currItem = subListEdit.get_Item(idx);
 | 
			
		||||
                        if (!currItem) continue;
 | 
			
		||||
                        auto currFc = FieldController::CreateSubFieldController(currItem);
 | 
			
		||||
 | 
			
		||||
                        std::string currSearchBaseKey = subBaseSearchKey;  // p_card-00-acc-0_002|0|produceDescriptions|
 | 
			
		||||
                        currSearchBaseKey.push_back('[');
 | 
			
		||||
                        currSearchBaseKey.append(std::to_string(idx));
 | 
			
		||||
                        currSearchBaseKey.append("]|");  // p_card-00-acc-0_002|0|produceDescriptions|[0]|
 | 
			
		||||
 | 
			
		||||
                        for (const auto& subLocalKey : subLocalKeys) {
 | 
			
		||||
                            std::string currSearchKey = currSearchBaseKey + subLocalKey;  // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
 | 
			
		||||
 | 
			
		||||
                            auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
 | 
			
		||||
 | 
			
		||||
                            /*
 | 
			
		||||
                            if (tableName == "ProduceStepEventDetail") {
 | 
			
		||||
                                Log::DebugFmt("localKeyType: %d currSearchKey: %s", localKeyType, currSearchKey.c_str());
 | 
			
		||||
                            }*/
 | 
			
		||||
 | 
			
		||||
                            if (localKeyType == JsonValueType::JVT_String) {
 | 
			
		||||
                                auto setData = GetTransString(currSearchKey, localData);
 | 
			
		||||
                                if (!setData.empty()) {
 | 
			
		||||
                                    currFc.SetStringField(subLocalKey, setData);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            else if (localKeyType == JsonValueType::JVT_ArrayString) {
 | 
			
		||||
                                auto setData = GetTransArrayString(currSearchKey, localData);
 | 
			
		||||
                                if (!setData.empty()) {
 | 
			
		||||
                                    currFc.SetStringListField(subLocalKey, setData);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                } break;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void LocalizeMasterItem(void* item, const std::string& tableName) {
 | 
			
		||||
        if (!Config::useMasterTrans) return;
 | 
			
		||||
        // Log::DebugFmt("LocalizeMasterItem: %s", tableName.c_str());
 | 
			
		||||
        FieldController fc(item);
 | 
			
		||||
        LocalizeMasterItem(fc, tableName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
} // namespace GakumasLocal::MasterLocal
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
#ifndef GAKUMAS_LOCALIFY_MASTERLOCAL_H
 | 
			
		||||
#define GAKUMAS_LOCALIFY_MASTERLOCAL_H
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::MasterLocal {
 | 
			
		||||
    void LoadData();
 | 
			
		||||
 | 
			
		||||
    void LocalizeMasterItem(void* item, const std::string& tableName);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //GAKUMAS_LOCALIFY_MASTERLOCAL_H
 | 
			
		||||
| 
						 | 
				
			
			@ -2,13 +2,36 @@
 | 
			
		|||
 | 
			
		||||
#include <codecvt>
 | 
			
		||||
#include <locale>
 | 
			
		||||
#include <jni.h>
 | 
			
		||||
#include "fmt/core.h"
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
    #include <jni.h>
 | 
			
		||||
    
 | 
			
		||||
extern JavaVM* g_javaVM;
 | 
			
		||||
    extern JavaVM* g_javaVM;
 | 
			
		||||
#else
 | 
			
		||||
    #include "cpprest/details/http_helpers.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::Misc {
 | 
			
		||||
 | 
			
		||||
#ifdef GKMS_WINDOWS
 | 
			
		||||
    std::string ToUTF8(const std::wstring_view& str) {
 | 
			
		||||
		return utility::conversions::to_utf8string(str.data());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::u16string ToUTF16(const std::string_view& str) {
 | 
			
		||||
        std::string input(str);
 | 
			
		||||
        std::wstring wstr = utility::conversions::utf8_to_utf16(input);
 | 
			
		||||
        return std::u16string(wstr.begin(), wstr.end());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string ToUTF8(const std::u16string_view& str) {
 | 
			
		||||
        std::u16string u16(str);
 | 
			
		||||
        std::wstring wstr(u16.begin(), u16.end());
 | 
			
		||||
        return utility::conversions::utf16_to_utf8(wstr);
 | 
			
		||||
    }
 | 
			
		||||
#else
 | 
			
		||||
    std::u16string ToUTF16(const std::string_view& str) {
 | 
			
		||||
        std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
 | 
			
		||||
        return utf16conv.from_bytes(str.data(), str.data() + str.size());
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +41,9 @@ namespace GakumasLocal::Misc {
 | 
			
		|||
        std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
 | 
			
		||||
        return utf16conv.to_bytes(str.data(), str.data() + str.size());
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
    JNIEnv* GetJNIEnv() {
 | 
			
		||||
        if (!g_javaVM) return nullptr;
 | 
			
		||||
        JNIEnv* env = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +55,7 @@ namespace GakumasLocal::Misc {
 | 
			
		|||
        }
 | 
			
		||||
        return env;
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    CSEnum::CSEnum(const std::string& name, const int value) {
 | 
			
		||||
        this->Add(name, value);
 | 
			
		||||
| 
						 | 
				
			
			@ -88,11 +114,112 @@ namespace GakumasLocal::Misc {
 | 
			
		|||
 | 
			
		||||
    int CSEnum::GetValueByName(const std::string &name) {
 | 
			
		||||
        for (int i = 0; i < names.size(); i++) {
 | 
			
		||||
            if (names[i].compare(name) == 0) {
 | 
			
		||||
            if (names[i] == name) {
 | 
			
		||||
                return values[i];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return values[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    namespace StringFormat {
 | 
			
		||||
        template<typename... Args>
 | 
			
		||||
        std::string string_format(const std::string& fmt, Args&&... args) {
 | 
			
		||||
            // return std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
 | 
			
		||||
            return fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        template <std::size_t N, std::size_t... Indices, typename T>
 | 
			
		||||
        auto vectorToTupleImpl(const std::vector<T>& vec, std::index_sequence<Indices...>) {
 | 
			
		||||
            if (vec.size() != N) {
 | 
			
		||||
                // printf("vec.size: %zu, N: %zu\n", vec.size(), N);
 | 
			
		||||
                throw std::out_of_range("Vector size does not match tuple size.");
 | 
			
		||||
            }
 | 
			
		||||
            return std::make_tuple(vec[Indices]...);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        template <std::size_t N, typename T>
 | 
			
		||||
        auto vectorToTuple(const std::vector<T>& vec) {
 | 
			
		||||
            return vectorToTupleImpl<N>(vec, std::make_index_sequence<N>{});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        template <typename T>
 | 
			
		||||
        std::string stringFormat(const std::string& fmt, const std::vector<T>& vec) {
 | 
			
		||||
            std::string ret = fmt;
 | 
			
		||||
 | 
			
		||||
#define CASE_ARG_COUNT(N) \
 | 
			
		||||
    case N: {\
 | 
			
		||||
        auto tp = vectorToTuple<N>(vec); \
 | 
			
		||||
        std::apply([&](auto&&... args) { \
 | 
			
		||||
            ret = string_format(fmt, args...); \
 | 
			
		||||
        }, tp); } break;
 | 
			
		||||
 | 
			
		||||
            switch (vec.size()) {
 | 
			
		||||
                CASE_ARG_COUNT(1)
 | 
			
		||||
                CASE_ARG_COUNT(2)
 | 
			
		||||
                CASE_ARG_COUNT(3)
 | 
			
		||||
                CASE_ARG_COUNT(4)
 | 
			
		||||
                CASE_ARG_COUNT(5)
 | 
			
		||||
                CASE_ARG_COUNT(6)
 | 
			
		||||
                CASE_ARG_COUNT(7)
 | 
			
		||||
                CASE_ARG_COUNT(8)
 | 
			
		||||
                CASE_ARG_COUNT(9)
 | 
			
		||||
                CASE_ARG_COUNT(10)
 | 
			
		||||
                CASE_ARG_COUNT(11)
 | 
			
		||||
                CASE_ARG_COUNT(12)
 | 
			
		||||
                CASE_ARG_COUNT(13)
 | 
			
		||||
                CASE_ARG_COUNT(14)
 | 
			
		||||
                CASE_ARG_COUNT(15)
 | 
			
		||||
                CASE_ARG_COUNT(16)
 | 
			
		||||
                CASE_ARG_COUNT(17)
 | 
			
		||||
                CASE_ARG_COUNT(18)
 | 
			
		||||
                CASE_ARG_COUNT(19)
 | 
			
		||||
                CASE_ARG_COUNT(20)
 | 
			
		||||
                CASE_ARG_COUNT(21)
 | 
			
		||||
                CASE_ARG_COUNT(22)
 | 
			
		||||
                CASE_ARG_COUNT(23)
 | 
			
		||||
                CASE_ARG_COUNT(24)
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec) {
 | 
			
		||||
            try {
 | 
			
		||||
                return stringFormat(fmt, vec);
 | 
			
		||||
            }
 | 
			
		||||
            catch (std::exception& e) {
 | 
			
		||||
                return fmt;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::vector<std::string> split(const std::string& str, char delimiter) {
 | 
			
		||||
            std::vector<std::string> result;
 | 
			
		||||
            std::string current;
 | 
			
		||||
            for (char c : str) {
 | 
			
		||||
                if (c == delimiter) {
 | 
			
		||||
                    if (!current.empty()) {
 | 
			
		||||
                        result.push_back(current);
 | 
			
		||||
                    }
 | 
			
		||||
                    current.clear();
 | 
			
		||||
                } else {
 | 
			
		||||
                    current += c;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!current.empty()) {
 | 
			
		||||
                result.push_back(current);
 | 
			
		||||
            }
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter) {
 | 
			
		||||
            size_t pos = str.find(delimiter);
 | 
			
		||||
            if (pos != std::string::npos) {
 | 
			
		||||
                return {str.substr(0, pos), str.substr(pos + delimiter.size())};
 | 
			
		||||
            }
 | 
			
		||||
            return {str, ""};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,11 +2,16 @@
 | 
			
		|||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <jni.h>
 | 
			
		||||
#include <deque>
 | 
			
		||||
#include <numeric>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "../platformDefine.hpp"
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
    #include <jni.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal {
 | 
			
		||||
    using OpaqueFunctionPointer = void (*)();
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +19,13 @@ namespace GakumasLocal {
 | 
			
		|||
    namespace Misc {
 | 
			
		||||
        std::u16string ToUTF16(const std::string_view& str);
 | 
			
		||||
        std::string ToUTF8(const std::u16string_view& str);
 | 
			
		||||
#ifdef GKMS_WINDOWS
 | 
			
		||||
        std::string ToUTF8(const std::wstring_view& str);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
        JNIEnv* GetJNIEnv();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
        class CSEnum {
 | 
			
		||||
        public:
 | 
			
		||||
| 
						 | 
				
			
			@ -73,5 +84,11 @@ namespace GakumasLocal {
 | 
			
		|||
            size_t maxSize;
 | 
			
		||||
            T sum;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        namespace StringFormat {
 | 
			
		||||
            std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec);
 | 
			
		||||
            std::vector<std::string> split(const std::string& str, char delimiter);
 | 
			
		||||
            std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,13 @@
 | 
			
		|||
#include "Misc.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <jni.h>
 | 
			
		||||
 | 
			
		||||
#include "../platformDefine.hpp"
 | 
			
		||||
 | 
			
		||||
#ifndef GKMS_WINDOWS
 | 
			
		||||
    #include <jni.h>
 | 
			
		||||
#endif // !GKMS_WINDOWS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal {
 | 
			
		||||
    struct HookInstaller
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,12 @@
 | 
			
		|||
#include "baseCamera.hpp"
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
#include "../../platformDefine.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef GKMS_WINDOWS
 | 
			
		||||
	#include <corecrt_math_defines.h>
 | 
			
		||||
#endif // GKMS_WINDOWS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace BaseCamera {
 | 
			
		||||
	using Vector3_t = UnityResolve::UnityType::Vector3;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../deps/UnityResolve/UnityResolve.hpp"
 | 
			
		||||
#include "../../deps/UnityResolve/UnityResolve.hpp"
 | 
			
		||||
 | 
			
		||||
enum LonMoveHState {
 | 
			
		||||
	LonMoveLeftAndRight,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,14 @@
 | 
			
		|||
#include "baseCamera.hpp"
 | 
			
		||||
#include "camera.hpp"
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include "Misc.hpp"
 | 
			
		||||
#include "../Misc.hpp"
 | 
			
		||||
#include "../BaseDefine.h"
 | 
			
		||||
#include "../../platformDefine.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef GKMS_WINDOWS
 | 
			
		||||
    #include <corecrt_math_defines.h>
 | 
			
		||||
#endif // GKMS_WINDOWS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace GKCamera {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include "baseCamera.hpp"
 | 
			
		||||
#include "Joystick/JoystickEvent.h"
 | 
			
		||||
#include "../../deps/Joystick/JoystickEvent.h"
 | 
			
		||||
 | 
			
		||||
namespace GKCamera {
 | 
			
		||||
    enum class CameraMode {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,25 +1,32 @@
 | 
			
		|||
#include <string>
 | 
			
		||||
#include "nlohmann/json.hpp"
 | 
			
		||||
#include "../Log.h"
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::Config {
 | 
			
		||||
    bool isConfigInit = false;
 | 
			
		||||
 | 
			
		||||
    bool dbgMode = false;
 | 
			
		||||
    bool enabled = true;
 | 
			
		||||
    bool lazyInit = true;
 | 
			
		||||
    bool replaceFont = true;
 | 
			
		||||
    bool forceExportResource = true;
 | 
			
		||||
    bool textTest = false;
 | 
			
		||||
    bool useMasterTrans = true;
 | 
			
		||||
    int gameOrientation = 0;
 | 
			
		||||
    bool dumpText = false;
 | 
			
		||||
    bool enableFreeCamera = false;
 | 
			
		||||
    int targetFrameRate = 0;
 | 
			
		||||
    bool unlockAllLive = false;
 | 
			
		||||
    bool unlockAllLiveCostume = false;
 | 
			
		||||
 | 
			
		||||
    bool enableLiveCustomeDress = false;
 | 
			
		||||
    std::string liveCustomeHeadId = "";
 | 
			
		||||
    std::string liveCustomeCostumeId = "";
 | 
			
		||||
 | 
			
		||||
    bool loginAsIOS = false;
 | 
			
		||||
 | 
			
		||||
    bool useCustomeGraphicSettings = false;
 | 
			
		||||
    float renderScale = 0.77f;
 | 
			
		||||
    int qualitySettingsLevel = 3;
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +54,8 @@ namespace GakumasLocal::Config {
 | 
			
		|||
    float bLimitZx = 1.0f;
 | 
			
		||||
    float bLimitZy = 1.0f;
 | 
			
		||||
 | 
			
		||||
    bool dmmUnlockSize = false;
 | 
			
		||||
 | 
			
		||||
    void LoadConfig(const std::string& configStr) {
 | 
			
		||||
        try {
 | 
			
		||||
            const auto config = nlohmann::json::parse(configStr);
 | 
			
		||||
| 
						 | 
				
			
			@ -55,17 +64,21 @@ namespace GakumasLocal::Config {
 | 
			
		|||
 | 
			
		||||
            GetConfigItem(dbgMode);
 | 
			
		||||
            GetConfigItem(enabled);
 | 
			
		||||
            GetConfigItem(lazyInit);
 | 
			
		||||
            GetConfigItem(replaceFont);
 | 
			
		||||
            GetConfigItem(forceExportResource);
 | 
			
		||||
            GetConfigItem(gameOrientation);
 | 
			
		||||
            GetConfigItem(textTest);
 | 
			
		||||
            GetConfigItem(useMasterTrans);
 | 
			
		||||
            GetConfigItem(dumpText);
 | 
			
		||||
            GetConfigItem(targetFrameRate);
 | 
			
		||||
            GetConfigItem(enableFreeCamera);
 | 
			
		||||
            GetConfigItem(unlockAllLive);
 | 
			
		||||
            GetConfigItem(unlockAllLiveCostume);
 | 
			
		||||
            GetConfigItem(enableLiveCustomeDress);
 | 
			
		||||
            GetConfigItem(liveCustomeHeadId);
 | 
			
		||||
            GetConfigItem(liveCustomeCostumeId);
 | 
			
		||||
            GetConfigItem(loginAsIOS);
 | 
			
		||||
            GetConfigItem(useCustomeGraphicSettings);
 | 
			
		||||
            GetConfigItem(renderScale);
 | 
			
		||||
            GetConfigItem(qualitySettingsLevel);
 | 
			
		||||
| 
						 | 
				
			
			@ -91,11 +104,74 @@ namespace GakumasLocal::Config {
 | 
			
		|||
            GetConfigItem(bLimitYy);
 | 
			
		||||
            GetConfigItem(bLimitZx);
 | 
			
		||||
            GetConfigItem(bLimitZy);
 | 
			
		||||
 | 
			
		||||
            GetConfigItem(dmmUnlockSize);
 | 
			
		||||
        }
 | 
			
		||||
        catch (std::exception& e) {
 | 
			
		||||
            Log::ErrorFmt("LoadConfig error: %s", e.what());
 | 
			
		||||
        }
 | 
			
		||||
        isConfigInit = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SaveConfig(const std::string& configPath) {
 | 
			
		||||
        try {
 | 
			
		||||
            nlohmann::json config;
 | 
			
		||||
 | 
			
		||||
            #define SetConfigItem(name) config[#name] = name
 | 
			
		||||
 | 
			
		||||
            SetConfigItem(dbgMode);
 | 
			
		||||
            SetConfigItem(enabled);
 | 
			
		||||
            SetConfigItem(lazyInit);
 | 
			
		||||
            SetConfigItem(replaceFont);
 | 
			
		||||
            SetConfigItem(forceExportResource);
 | 
			
		||||
            SetConfigItem(gameOrientation);
 | 
			
		||||
            SetConfigItem(textTest);
 | 
			
		||||
            SetConfigItem(useMasterTrans);
 | 
			
		||||
            SetConfigItem(dumpText);
 | 
			
		||||
            SetConfigItem(targetFrameRate);
 | 
			
		||||
            SetConfigItem(enableFreeCamera);
 | 
			
		||||
            SetConfigItem(unlockAllLive);
 | 
			
		||||
            SetConfigItem(unlockAllLiveCostume);
 | 
			
		||||
            SetConfigItem(enableLiveCustomeDress);
 | 
			
		||||
            SetConfigItem(liveCustomeHeadId);
 | 
			
		||||
            SetConfigItem(liveCustomeCostumeId);
 | 
			
		||||
            SetConfigItem(loginAsIOS);
 | 
			
		||||
            SetConfigItem(useCustomeGraphicSettings);
 | 
			
		||||
            SetConfigItem(renderScale);
 | 
			
		||||
            SetConfigItem(qualitySettingsLevel);
 | 
			
		||||
            SetConfigItem(volumeIndex);
 | 
			
		||||
            SetConfigItem(maxBufferPixel);
 | 
			
		||||
            SetConfigItem(reflectionQualityLevel);
 | 
			
		||||
            SetConfigItem(lodQualityLevel);
 | 
			
		||||
            SetConfigItem(enableBreastParam);
 | 
			
		||||
            SetConfigItem(bDamping);
 | 
			
		||||
            SetConfigItem(bStiffness);
 | 
			
		||||
            SetConfigItem(bSpring);
 | 
			
		||||
            SetConfigItem(bPendulum);
 | 
			
		||||
            SetConfigItem(bPendulumRange);
 | 
			
		||||
            SetConfigItem(bAverage);
 | 
			
		||||
            SetConfigItem(bRootWeight);
 | 
			
		||||
            SetConfigItem(bUseArmCorrection);
 | 
			
		||||
            SetConfigItem(bUseScale);
 | 
			
		||||
            SetConfigItem(bScale);
 | 
			
		||||
            SetConfigItem(bUseLimit);
 | 
			
		||||
            SetConfigItem(bLimitXx);
 | 
			
		||||
            SetConfigItem(bLimitXy);
 | 
			
		||||
            SetConfigItem(bLimitYx);
 | 
			
		||||
            SetConfigItem(bLimitYy);
 | 
			
		||||
            SetConfigItem(bLimitZx);
 | 
			
		||||
            SetConfigItem(bLimitZy);
 | 
			
		||||
            SetConfigItem(dmmUnlockSize);
 | 
			
		||||
 | 
			
		||||
            std::ofstream out(configPath);
 | 
			
		||||
            if (!out) {
 | 
			
		||||
                Log::ErrorFmt("SaveConfig error: Cannot open file: %s", configPath.c_str());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            out << config.dump(4);
 | 
			
		||||
			Log::Info("SaveConfig success");
 | 
			
		||||
        }
 | 
			
		||||
        catch (std::exception& e) {
 | 
			
		||||
            Log::ErrorFmt("SaveConfig error: %s", e.what());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,19 +5,24 @@ namespace GakumasLocal::Config {
 | 
			
		|||
 | 
			
		||||
    extern bool dbgMode;
 | 
			
		||||
    extern bool enabled;
 | 
			
		||||
    extern bool lazyInit;
 | 
			
		||||
    extern bool replaceFont;
 | 
			
		||||
    extern bool forceExportResource;
 | 
			
		||||
    extern int gameOrientation;
 | 
			
		||||
    extern bool textTest;
 | 
			
		||||
    extern bool useMasterTrans;
 | 
			
		||||
    extern bool dumpText;
 | 
			
		||||
    extern bool enableFreeCamera;
 | 
			
		||||
    extern int targetFrameRate;
 | 
			
		||||
    extern bool unlockAllLive;
 | 
			
		||||
    extern bool unlockAllLiveCostume;
 | 
			
		||||
 | 
			
		||||
    extern bool enableLiveCustomeDress;
 | 
			
		||||
    extern std::string liveCustomeHeadId;
 | 
			
		||||
    extern std::string liveCustomeCostumeId;
 | 
			
		||||
 | 
			
		||||
    extern bool loginAsIOS;
 | 
			
		||||
 | 
			
		||||
    extern bool useCustomeGraphicSettings;
 | 
			
		||||
    extern float renderScale;
 | 
			
		||||
    extern int qualitySettingsLevel;
 | 
			
		||||
| 
						 | 
				
			
			@ -46,5 +51,8 @@ namespace GakumasLocal::Config {
 | 
			
		|||
    extern float bLimitZx;
 | 
			
		||||
    extern float bLimitZy;
 | 
			
		||||
 | 
			
		||||
    extern bool dmmUnlockSize;
 | 
			
		||||
 | 
			
		||||
    void LoadConfig(const std::string& configStr);
 | 
			
		||||
    void SaveConfig(const std::string& configPath);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,123 @@
 | 
			
		|||
#include <unordered_set>
 | 
			
		||||
#include "StringParser.hpp"
 | 
			
		||||
#include "fmt/core.h"
 | 
			
		||||
#include "fmt/ranges.h"
 | 
			
		||||
#include "../Misc.hpp"
 | 
			
		||||
 | 
			
		||||
namespace StringParser {
 | 
			
		||||
 | 
			
		||||
    std::string ParseItems::ToFmtString() {
 | 
			
		||||
        std::vector<std::string> ret{};
 | 
			
		||||
        int currFlagIndex = 0;
 | 
			
		||||
        for (const auto& i : items) {
 | 
			
		||||
            if (i.type == ParseItemType::FLAG) {
 | 
			
		||||
                ret.push_back(fmt::format("{{{}}}", currFlagIndex));
 | 
			
		||||
                currFlagIndex++;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                ret.push_back(i.content);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return fmt::format("{}", fmt::join(ret, ""));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<std::string> ParseItems::GetFlagValues() {
 | 
			
		||||
        std::vector<std::string> ret{};
 | 
			
		||||
        for (const auto& i : items) {
 | 
			
		||||
            if (i.type == ParseItemType::FLAG) {
 | 
			
		||||
                ret.push_back(i.content);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ParseItems ParseItems::parse(const std::string &str, bool parseTags) {
 | 
			
		||||
        static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
 | 
			
		||||
                                                                u'6', u'7', u'8', u'9', u'+', u'+',
 | 
			
		||||
                                                                u'-', u'-', u'%', u'%',
 | 
			
		||||
                                                                u'.', u'×', u',', u','};
 | 
			
		||||
 | 
			
		||||
        ParseItems result;
 | 
			
		||||
        if (str.contains("{")) {
 | 
			
		||||
            result.isValid = false;
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
        std::u16string origText = GakumasLocal::Misc::ToUTF16(str);
 | 
			
		||||
        bool isInTag = false;
 | 
			
		||||
        bool isInFlagSequence = false;
 | 
			
		||||
        std::u16string currentCacheText;
 | 
			
		||||
 | 
			
		||||
        for (char16_t currChar : origText) {
 | 
			
		||||
            if (parseTags && currChar == u'<') {
 | 
			
		||||
                if (!currentCacheText.empty()) {
 | 
			
		||||
                    result.items.push_back({isInFlagSequence ? ParseItemType::FLAG : ParseItemType::TEXT,
 | 
			
		||||
                                            GakumasLocal::Misc::ToUTF8(currentCacheText)});
 | 
			
		||||
                    currentCacheText.clear();
 | 
			
		||||
                    isInFlagSequence = false;
 | 
			
		||||
                }
 | 
			
		||||
                isInTag = true;
 | 
			
		||||
                currentCacheText.push_back(currChar);
 | 
			
		||||
            } else if (parseTags && currChar == u'>') {
 | 
			
		||||
                isInTag = false;
 | 
			
		||||
                currentCacheText.push_back(currChar);
 | 
			
		||||
                result.items.push_back({ParseItemType::FLAG, GakumasLocal::Misc::ToUTF8(currentCacheText)});
 | 
			
		||||
                currentCacheText.clear();
 | 
			
		||||
            } else if (isInTag) {
 | 
			
		||||
                currentCacheText.push_back(currChar);
 | 
			
		||||
            } else if (splitFlags.contains(currChar)) {
 | 
			
		||||
                if (!isInFlagSequence && !currentCacheText.empty()) {
 | 
			
		||||
                    result.items.push_back({ParseItemType::TEXT, GakumasLocal::Misc::ToUTF8(currentCacheText)});
 | 
			
		||||
                    currentCacheText.clear();
 | 
			
		||||
                }
 | 
			
		||||
                isInFlagSequence = true;
 | 
			
		||||
                currentCacheText.push_back(currChar);
 | 
			
		||||
            } else {
 | 
			
		||||
                if (isInFlagSequence && !currentCacheText.empty()) {
 | 
			
		||||
                    result.items.push_back({ParseItemType::FLAG, GakumasLocal::Misc::ToUTF8(currentCacheText)});
 | 
			
		||||
                    currentCacheText.clear();
 | 
			
		||||
                    isInFlagSequence = false;
 | 
			
		||||
                }
 | 
			
		||||
                currentCacheText.push_back(currChar);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!currentCacheText.empty()) {
 | 
			
		||||
            result.items.push_back({isInFlagSequence ? ParseItemType::FLAG : ParseItemType::TEXT,
 | 
			
		||||
                                    GakumasLocal::Misc::ToUTF8(currentCacheText)});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (auto& i : result.items) {
 | 
			
		||||
            if (i.type == ParseItemType::FLAG) {
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        result.isValid = false;
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string ParseItems::MergeText(ParseItems &textTarget, ParseItems &valueTarget) {
 | 
			
		||||
        if (!textTarget.isValid) return "";
 | 
			
		||||
        if (!valueTarget.isValid) return "";
 | 
			
		||||
        const auto fmtText = textTarget.ToFmtString();
 | 
			
		||||
        const auto values = valueTarget.GetFlagValues();
 | 
			
		||||
        const std::string ret = GakumasLocal::Misc::StringFormat::stringFormatString(fmtText, values);
 | 
			
		||||
        return {ret.begin(), ret.end()};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string ParseItems::MergeText(const std::string &newStr) {
 | 
			
		||||
        if (!isValid) return "";
 | 
			
		||||
        const auto values = GetFlagValues();
 | 
			
		||||
        return GakumasLocal::Misc::StringFormat::stringFormatString(newStr, values);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int ParseItems::GetFlagCount() {
 | 
			
		||||
        int ret = 0;
 | 
			
		||||
        for (auto& i : items) {
 | 
			
		||||
            if (i.type == ParseItemType::FLAG) {
 | 
			
		||||
                ret++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace StringParser {
 | 
			
		||||
    enum class ParseItemType {
 | 
			
		||||
        FLAG,
 | 
			
		||||
        TEXT
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct ParseItem {
 | 
			
		||||
        ParseItemType type;
 | 
			
		||||
        std::string content;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct ParseItems {
 | 
			
		||||
        bool isValid = true;
 | 
			
		||||
        std::vector<ParseItem> items;
 | 
			
		||||
 | 
			
		||||
        std::string ToFmtString();
 | 
			
		||||
        std::vector<std::string> GetFlagValues();
 | 
			
		||||
        std::string MergeText(const std::string& newStr);
 | 
			
		||||
        int GetFlagCount();
 | 
			
		||||
 | 
			
		||||
        static ParseItems parse(const std::string& str, bool parseTags);
 | 
			
		||||
        static std::string MergeText(ParseItems& textTarget, ParseItems& valueTarget);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +47,18 @@
 | 
			
		|||
#include "../../GakumasLocalify/Log.h"
 | 
			
		||||
#include "../../GakumasLocalify/Misc.hpp"
 | 
			
		||||
 | 
			
		||||
class UnityResolveProgress final {
 | 
			
		||||
public:
 | 
			
		||||
    struct Progress {
 | 
			
		||||
        long current = 0;
 | 
			
		||||
        long total = 1;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static bool startInit;
 | 
			
		||||
    static Progress assembliesProgress;
 | 
			
		||||
    static Progress classProgress;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class UnityResolve final {
 | 
			
		||||
public:
 | 
			
		||||
	struct Assembly;
 | 
			
		||||
| 
						 | 
				
			
			@ -69,8 +81,16 @@ public:
 | 
			
		|||
 | 
			
		||||
		[[nodiscard]] auto Get(const std::string& strClass, const std::string& strNamespace = "*", const std::string& strParent = "*") const -> Class* {
 | 
			
		||||
			if (!this) return nullptr;
 | 
			
		||||
            /*
 | 
			
		||||
            if (lazyInit_ && classes.empty()) {
 | 
			
		||||
                const auto image = Invoke<void*>("il2cpp_assembly_get_image", address);
 | 
			
		||||
                ForeachClass(const_cast<Assembly *>(this), image);
 | 
			
		||||
            }*/
 | 
			
		||||
			for (const auto pClass : classes) if (strClass == pClass->name && (strNamespace == "*" || pClass->namespaze == strNamespace) && (strParent == "*" || pClass->parent == strParent)) return pClass;
 | 
			
		||||
			return nullptr;
 | 
			
		||||
			if (lazyInit_) {
 | 
			
		||||
                return FillClass_Il2ccpp(const_cast<Assembly *>(this), strNamespace.c_str(), strClass.c_str());
 | 
			
		||||
            }
 | 
			
		||||
            return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -279,14 +299,17 @@ public:
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static auto Init(void* hmodule, const Mode mode = Mode::Mono) -> void {
 | 
			
		||||
	static auto Init(void* hmodule, const Mode mode = Mode::Mono, const bool lazyInit = false) -> void {
 | 
			
		||||
		mode_ = mode;
 | 
			
		||||
		hmodule_ = hmodule;
 | 
			
		||||
        lazyInit_ = lazyInit;
 | 
			
		||||
 | 
			
		||||
		if (mode_ == Mode::Il2Cpp) {
 | 
			
		||||
            if (!lazyInit) UnityResolveProgress::startInit = true;
 | 
			
		||||
			pDomain = Invoke<void*>("il2cpp_domain_get");
 | 
			
		||||
			Invoke<void*>("il2cpp_thread_attach", pDomain);
 | 
			
		||||
			ForeachAssembly();
 | 
			
		||||
            // if (!lazyInit) UnityResolveProgress::startInit = false;
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			pDomain = Invoke<void*>("mono_get_root_domain");
 | 
			
		||||
| 
						 | 
				
			
			@ -561,7 +584,11 @@ private:
 | 
			
		|||
		if (mode_ == Mode::Il2Cpp) {
 | 
			
		||||
			size_t     nrofassemblies = 0;
 | 
			
		||||
			const auto assemblies = Invoke<void**>("il2cpp_domain_get_assemblies", pDomain, &nrofassemblies);
 | 
			
		||||
 | 
			
		||||
            if (!lazyInit_) UnityResolveProgress::assembliesProgress.total = nrofassemblies;
 | 
			
		||||
 | 
			
		||||
			for (auto i = 0; i < nrofassemblies; i++) {
 | 
			
		||||
                if (!lazyInit_) UnityResolveProgress::assembliesProgress.current = i + 1;
 | 
			
		||||
				const auto ptr = assemblies[i];
 | 
			
		||||
				if (ptr == nullptr) continue;
 | 
			
		||||
				auto       assembly = new Assembly{ .address = ptr };
 | 
			
		||||
| 
						 | 
				
			
			@ -569,7 +596,9 @@ private:
 | 
			
		|||
				assembly->file = Invoke<const char*>("il2cpp_image_get_filename", image);
 | 
			
		||||
				assembly->name = Invoke<const char*>("il2cpp_image_get_name", image);
 | 
			
		||||
				UnityResolve::assembly.push_back(assembly);
 | 
			
		||||
				ForeachClass(assembly, image);
 | 
			
		||||
                if (!lazyInit_) {
 | 
			
		||||
                    ForeachClass(assembly, image);
 | 
			
		||||
                }
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
| 
						 | 
				
			
			@ -590,11 +619,60 @@ private:
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    static auto GetPClassFromUnknownNamespace(void* image, const char* klassName) -> void* {
 | 
			
		||||
        const auto count = Invoke<int>("il2cpp_image_get_class_count", image);
 | 
			
		||||
        for (auto i = 0; i < count; i++) {
 | 
			
		||||
            const auto pClass = Invoke<void*>("il2cpp_image_get_class", image, i);
 | 
			
		||||
            const auto className = Invoke<const char*>("il2cpp_class_get_name", pClass);
 | 
			
		||||
            if (strcmp(className, klassName) == 0) {
 | 
			
		||||
                return pClass;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static auto FillClass_Il2ccpp(Assembly* assembly, const char* namespaze, const char* klassName) -> Class* {
 | 
			
		||||
        auto image = Invoke<void*>("il2cpp_assembly_get_image", assembly->address);
 | 
			
		||||
        void* pClass;
 | 
			
		||||
        if (strcmp(namespaze, "*") == 0) {
 | 
			
		||||
            pClass = GetPClassFromUnknownNamespace(image, klassName);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            pClass = Invoke<void*>("il2cpp_class_from_name", image, namespaze, klassName);
 | 
			
		||||
        }
 | 
			
		||||
        if (!pClass && (strlen(namespaze) == 0)) {
 | 
			
		||||
            pClass = GetPClassFromUnknownNamespace(image, klassName);
 | 
			
		||||
        }
 | 
			
		||||
        if (pClass == nullptr) return nullptr;
 | 
			
		||||
        const auto pAClass = new Class();
 | 
			
		||||
        pAClass->address = pClass;
 | 
			
		||||
        pAClass->name = Invoke<const char*>("il2cpp_class_get_name", pClass);
 | 
			
		||||
        if (const auto pPClass = Invoke<void*>("il2cpp_class_get_parent", pClass)) pAClass->parent = Invoke<const char*>("il2cpp_class_get_name", pPClass);
 | 
			
		||||
        // pAClass->namespaze = Invoke<const char*>("il2cpp_class_get_namespace", pClass);
 | 
			
		||||
        pAClass->namespaze = namespaze;
 | 
			
		||||
        assembly->classes.push_back(pAClass);
 | 
			
		||||
 | 
			
		||||
        ForeachFields(pAClass, pClass);
 | 
			
		||||
        ForeachMethod(pAClass, pClass);
 | 
			
		||||
 | 
			
		||||
        void* i_class{};
 | 
			
		||||
        void* iter{};
 | 
			
		||||
        do {
 | 
			
		||||
            if ((i_class = Invoke<void*>("il2cpp_class_get_interfaces", pClass, &iter))) {
 | 
			
		||||
                ForeachFields(pAClass, i_class);
 | 
			
		||||
                ForeachMethod(pAClass, i_class);
 | 
			
		||||
            }
 | 
			
		||||
        } while (i_class);
 | 
			
		||||
        return pAClass;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	static auto ForeachClass(Assembly* assembly, void* image) -> void {
 | 
			
		||||
		// 遍历类
 | 
			
		||||
		if (mode_ == Mode::Il2Cpp) {
 | 
			
		||||
			const auto count = Invoke<int>("il2cpp_image_get_class_count", image);
 | 
			
		||||
            if (!lazyInit_) UnityResolveProgress::classProgress.total = count;
 | 
			
		||||
			for (auto i = 0; i < count; i++) {
 | 
			
		||||
                if (!lazyInit_) UnityResolveProgress::classProgress.current = i + 1;
 | 
			
		||||
				const auto pClass = Invoke<void*>("il2cpp_image_get_class", image, i);
 | 
			
		||||
				if (pClass == nullptr) continue;
 | 
			
		||||
				const auto pAClass = new Class();
 | 
			
		||||
| 
						 | 
				
			
			@ -1393,6 +1471,25 @@ public:
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
            [[nodiscard]] auto ToWString() const -> std::u16string {
 | 
			
		||||
#if WINDOWS_MODE
 | 
			
		||||
                if (IsBadReadPtr(this, sizeof(String))) return {};
 | 
			
		||||
				if (IsBadReadPtr(m_firstChar, m_stringLength)) return {};
 | 
			
		||||
#endif
 | 
			
		||||
                if (!this) return {};
 | 
			
		||||
                try {
 | 
			
		||||
                    // using convert_typeX = std::codecvt_utf8<wchar_t>;
 | 
			
		||||
                    // std::wstring_convert<convert_typeX> converterX;
 | 
			
		||||
                    // return converterX.to_bytes(m_firstChar);
 | 
			
		||||
                    return {chars};
 | 
			
		||||
                }
 | 
			
		||||
                catch (std::exception& e) {
 | 
			
		||||
                    std::cout << "String Invoke Error\n";
 | 
			
		||||
                    GakumasLocal::Log::ErrorFmt("String Invoke Error: %s", e.what());
 | 
			
		||||
                    return {};
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
			auto operator=(const std::string& newString) const -> String* { return New(newString); }
 | 
			
		||||
 | 
			
		||||
			auto operator==(const std::wstring& newString) const -> bool { return Equals(newString); }
 | 
			
		||||
| 
						 | 
				
			
			@ -2586,6 +2683,7 @@ public:
 | 
			
		|||
private:
 | 
			
		||||
	inline static Mode                                   mode_{};
 | 
			
		||||
	inline static void* hmodule_;
 | 
			
		||||
	inline static bool lazyInit_;
 | 
			
		||||
	inline static std::unordered_map<std::string, void*> address_{};
 | 
			
		||||
	inline static void* pDomain{};
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,228 @@
 | 
			
		|||
// Formatting library for C++ - dynamic argument lists
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - present, Victor Zverovich
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_ARGS_H_
 | 
			
		||||
#define FMT_ARGS_H_
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <functional>  // std::reference_wrapper
 | 
			
		||||
#  include <memory>      // std::unique_ptr
 | 
			
		||||
#  include <vector>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "format.h"  // std_string_view
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename T> struct is_reference_wrapper : std::false_type {};
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
template <typename T> auto unwrap(const T& v) -> const T& { return v; }
 | 
			
		||||
template <typename T>
 | 
			
		||||
auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
 | 
			
		||||
  return static_cast<const T&>(v);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC
 | 
			
		||||
// 2022 (v17.10.0).
 | 
			
		||||
//
 | 
			
		||||
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
 | 
			
		||||
// templates it doesn't complain about inability to deduce single translation
 | 
			
		||||
// unit for placing vtable. So node is made a fake template.
 | 
			
		||||
template <typename = void> struct node {
 | 
			
		||||
  virtual ~node() = default;
 | 
			
		||||
  std::unique_ptr<node<>> next;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class dynamic_arg_list {
 | 
			
		||||
  template <typename T> struct typed_node : node<> {
 | 
			
		||||
    T value;
 | 
			
		||||
 | 
			
		||||
    template <typename Arg>
 | 
			
		||||
    FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
 | 
			
		||||
 | 
			
		||||
    template <typename Char>
 | 
			
		||||
    FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
 | 
			
		||||
        : value(arg.data(), arg.size()) {}
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  std::unique_ptr<node<>> head_;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
 | 
			
		||||
    auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
 | 
			
		||||
    auto& value = new_node->value;
 | 
			
		||||
    new_node->next = std::move(head_);
 | 
			
		||||
    head_ = std::move(new_node);
 | 
			
		||||
    return value;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A dynamic list of formatting arguments with storage.
 | 
			
		||||
 *
 | 
			
		||||
 * It can be implicitly converted into `fmt::basic_format_args` for passing
 | 
			
		||||
 * into type-erased formatting functions such as `fmt::vformat`.
 | 
			
		||||
 */
 | 
			
		||||
template <typename Context>
 | 
			
		||||
class dynamic_format_arg_store
 | 
			
		||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
 | 
			
		||||
    // Workaround a GCC template argument substitution bug.
 | 
			
		||||
    : public basic_format_args<Context>
 | 
			
		||||
#endif
 | 
			
		||||
{
 | 
			
		||||
 private:
 | 
			
		||||
  using char_type = typename Context::char_type;
 | 
			
		||||
 | 
			
		||||
  template <typename T> struct need_copy {
 | 
			
		||||
    static constexpr detail::type mapped_type =
 | 
			
		||||
        detail::mapped_type_constant<T, Context>::value;
 | 
			
		||||
 | 
			
		||||
    enum {
 | 
			
		||||
      value = !(detail::is_reference_wrapper<T>::value ||
 | 
			
		||||
                std::is_same<T, basic_string_view<char_type>>::value ||
 | 
			
		||||
                std::is_same<T, detail::std_string_view<char_type>>::value ||
 | 
			
		||||
                (mapped_type != detail::type::cstring_type &&
 | 
			
		||||
                 mapped_type != detail::type::string_type &&
 | 
			
		||||
                 mapped_type != detail::type::custom_type))
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  using stored_type = conditional_t<
 | 
			
		||||
      std::is_convertible<T, std::basic_string<char_type>>::value &&
 | 
			
		||||
          !detail::is_reference_wrapper<T>::value,
 | 
			
		||||
      std::basic_string<char_type>, T>;
 | 
			
		||||
 | 
			
		||||
  // Storage of basic_format_arg must be contiguous.
 | 
			
		||||
  std::vector<basic_format_arg<Context>> data_;
 | 
			
		||||
  std::vector<detail::named_arg_info<char_type>> named_info_;
 | 
			
		||||
 | 
			
		||||
  // Storage of arguments not fitting into basic_format_arg must grow
 | 
			
		||||
  // without relocation because items in data_ refer to it.
 | 
			
		||||
  detail::dynamic_arg_list dynamic_args_;
 | 
			
		||||
 | 
			
		||||
  friend class basic_format_args<Context>;
 | 
			
		||||
 | 
			
		||||
  auto get_types() const -> unsigned long long {
 | 
			
		||||
    return detail::is_unpacked_bit | data_.size() |
 | 
			
		||||
           (named_info_.empty()
 | 
			
		||||
                ? 0ULL
 | 
			
		||||
                : static_cast<unsigned long long>(detail::has_named_args_bit));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto data() const -> const basic_format_arg<Context>* {
 | 
			
		||||
    return named_info_.empty() ? data_.data() : data_.data() + 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T> void emplace_arg(const T& arg) {
 | 
			
		||||
    data_.emplace_back(detail::make_arg<Context>(arg));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  void emplace_arg(const detail::named_arg<char_type, T>& arg) {
 | 
			
		||||
    if (named_info_.empty()) {
 | 
			
		||||
      constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
 | 
			
		||||
      data_.insert(data_.begin(), {zero_ptr, 0});
 | 
			
		||||
    }
 | 
			
		||||
    data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
 | 
			
		||||
    auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
 | 
			
		||||
      data->pop_back();
 | 
			
		||||
    };
 | 
			
		||||
    std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
 | 
			
		||||
        guard{&data_, pop_one};
 | 
			
		||||
    named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
 | 
			
		||||
    data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
 | 
			
		||||
    guard.release();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  constexpr dynamic_format_arg_store() = default;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds an argument into the dynamic store for later passing to a formatting
 | 
			
		||||
   * function.
 | 
			
		||||
   *
 | 
			
		||||
   * Note that custom types and string types (but not string views) are copied
 | 
			
		||||
   * into the store dynamically allocating memory if necessary.
 | 
			
		||||
   *
 | 
			
		||||
   * **Example**:
 | 
			
		||||
   *
 | 
			
		||||
   *     fmt::dynamic_format_arg_store<fmt::format_context> store;
 | 
			
		||||
   *     store.push_back(42);
 | 
			
		||||
   *     store.push_back("abc");
 | 
			
		||||
   *     store.push_back(1.5f);
 | 
			
		||||
   *     std::string result = fmt::vformat("{} and {} and {}", store);
 | 
			
		||||
   */
 | 
			
		||||
  template <typename T> void push_back(const T& arg) {
 | 
			
		||||
    if (detail::const_check(need_copy<T>::value))
 | 
			
		||||
      emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
 | 
			
		||||
    else
 | 
			
		||||
      emplace_arg(detail::unwrap(arg));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds a reference to the argument into the dynamic store for later passing
 | 
			
		||||
   * to a formatting function.
 | 
			
		||||
   *
 | 
			
		||||
   * **Example**:
 | 
			
		||||
   *
 | 
			
		||||
   *     fmt::dynamic_format_arg_store<fmt::format_context> store;
 | 
			
		||||
   *     char band[] = "Rolling Stones";
 | 
			
		||||
   *     store.push_back(std::cref(band));
 | 
			
		||||
   *     band[9] = 'c'; // Changing str affects the output.
 | 
			
		||||
   *     std::string result = fmt::vformat("{}", store);
 | 
			
		||||
   *     // result == "Rolling Scones"
 | 
			
		||||
   */
 | 
			
		||||
  template <typename T> void push_back(std::reference_wrapper<T> arg) {
 | 
			
		||||
    static_assert(
 | 
			
		||||
        need_copy<T>::value,
 | 
			
		||||
        "objects of built-in types and string views are always copied");
 | 
			
		||||
    emplace_arg(arg.get());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds named argument into the dynamic store for later passing to a
 | 
			
		||||
   * formatting function. `std::reference_wrapper` is supported to avoid
 | 
			
		||||
   * copying of the argument. The name is always copied into the store.
 | 
			
		||||
   */
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  void push_back(const detail::named_arg<char_type, T>& arg) {
 | 
			
		||||
    const char_type* arg_name =
 | 
			
		||||
        dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
 | 
			
		||||
    if (detail::const_check(need_copy<T>::value)) {
 | 
			
		||||
      emplace_arg(
 | 
			
		||||
          fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
 | 
			
		||||
    } else {
 | 
			
		||||
      emplace_arg(fmt::arg(arg_name, arg.value));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Erase all elements from the store.
 | 
			
		||||
  void clear() {
 | 
			
		||||
    data_.clear();
 | 
			
		||||
    named_info_.clear();
 | 
			
		||||
    dynamic_args_ = detail::dynamic_arg_list();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Reserves space to store at least `new_cap` arguments including
 | 
			
		||||
  /// `new_cap_named` named arguments.
 | 
			
		||||
  void reserve(size_t new_cap, size_t new_cap_named) {
 | 
			
		||||
    FMT_ASSERT(new_cap >= new_cap_named,
 | 
			
		||||
               "Set of arguments includes set of named arguments");
 | 
			
		||||
    data_.reserve(new_cap);
 | 
			
		||||
    named_info_.reserve(new_cap_named);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#endif  // FMT_ARGS_H_
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,612 @@
 | 
			
		|||
// Formatting library for C++ - color support
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_COLOR_H_
 | 
			
		||||
#define FMT_COLOR_H_
 | 
			
		||||
 | 
			
		||||
#include "format.h"
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
FMT_BEGIN_EXPORT
 | 
			
		||||
 | 
			
		||||
enum class color : uint32_t {
 | 
			
		||||
  alice_blue = 0xF0F8FF,               // rgb(240,248,255)
 | 
			
		||||
  antique_white = 0xFAEBD7,            // rgb(250,235,215)
 | 
			
		||||
  aqua = 0x00FFFF,                     // rgb(0,255,255)
 | 
			
		||||
  aquamarine = 0x7FFFD4,               // rgb(127,255,212)
 | 
			
		||||
  azure = 0xF0FFFF,                    // rgb(240,255,255)
 | 
			
		||||
  beige = 0xF5F5DC,                    // rgb(245,245,220)
 | 
			
		||||
  bisque = 0xFFE4C4,                   // rgb(255,228,196)
 | 
			
		||||
  black = 0x000000,                    // rgb(0,0,0)
 | 
			
		||||
  blanched_almond = 0xFFEBCD,          // rgb(255,235,205)
 | 
			
		||||
  blue = 0x0000FF,                     // rgb(0,0,255)
 | 
			
		||||
  blue_violet = 0x8A2BE2,              // rgb(138,43,226)
 | 
			
		||||
  brown = 0xA52A2A,                    // rgb(165,42,42)
 | 
			
		||||
  burly_wood = 0xDEB887,               // rgb(222,184,135)
 | 
			
		||||
  cadet_blue = 0x5F9EA0,               // rgb(95,158,160)
 | 
			
		||||
  chartreuse = 0x7FFF00,               // rgb(127,255,0)
 | 
			
		||||
  chocolate = 0xD2691E,                // rgb(210,105,30)
 | 
			
		||||
  coral = 0xFF7F50,                    // rgb(255,127,80)
 | 
			
		||||
  cornflower_blue = 0x6495ED,          // rgb(100,149,237)
 | 
			
		||||
  cornsilk = 0xFFF8DC,                 // rgb(255,248,220)
 | 
			
		||||
  crimson = 0xDC143C,                  // rgb(220,20,60)
 | 
			
		||||
  cyan = 0x00FFFF,                     // rgb(0,255,255)
 | 
			
		||||
  dark_blue = 0x00008B,                // rgb(0,0,139)
 | 
			
		||||
  dark_cyan = 0x008B8B,                // rgb(0,139,139)
 | 
			
		||||
  dark_golden_rod = 0xB8860B,          // rgb(184,134,11)
 | 
			
		||||
  dark_gray = 0xA9A9A9,                // rgb(169,169,169)
 | 
			
		||||
  dark_green = 0x006400,               // rgb(0,100,0)
 | 
			
		||||
  dark_khaki = 0xBDB76B,               // rgb(189,183,107)
 | 
			
		||||
  dark_magenta = 0x8B008B,             // rgb(139,0,139)
 | 
			
		||||
  dark_olive_green = 0x556B2F,         // rgb(85,107,47)
 | 
			
		||||
  dark_orange = 0xFF8C00,              // rgb(255,140,0)
 | 
			
		||||
  dark_orchid = 0x9932CC,              // rgb(153,50,204)
 | 
			
		||||
  dark_red = 0x8B0000,                 // rgb(139,0,0)
 | 
			
		||||
  dark_salmon = 0xE9967A,              // rgb(233,150,122)
 | 
			
		||||
  dark_sea_green = 0x8FBC8F,           // rgb(143,188,143)
 | 
			
		||||
  dark_slate_blue = 0x483D8B,          // rgb(72,61,139)
 | 
			
		||||
  dark_slate_gray = 0x2F4F4F,          // rgb(47,79,79)
 | 
			
		||||
  dark_turquoise = 0x00CED1,           // rgb(0,206,209)
 | 
			
		||||
  dark_violet = 0x9400D3,              // rgb(148,0,211)
 | 
			
		||||
  deep_pink = 0xFF1493,                // rgb(255,20,147)
 | 
			
		||||
  deep_sky_blue = 0x00BFFF,            // rgb(0,191,255)
 | 
			
		||||
  dim_gray = 0x696969,                 // rgb(105,105,105)
 | 
			
		||||
  dodger_blue = 0x1E90FF,              // rgb(30,144,255)
 | 
			
		||||
  fire_brick = 0xB22222,               // rgb(178,34,34)
 | 
			
		||||
  floral_white = 0xFFFAF0,             // rgb(255,250,240)
 | 
			
		||||
  forest_green = 0x228B22,             // rgb(34,139,34)
 | 
			
		||||
  fuchsia = 0xFF00FF,                  // rgb(255,0,255)
 | 
			
		||||
  gainsboro = 0xDCDCDC,                // rgb(220,220,220)
 | 
			
		||||
  ghost_white = 0xF8F8FF,              // rgb(248,248,255)
 | 
			
		||||
  gold = 0xFFD700,                     // rgb(255,215,0)
 | 
			
		||||
  golden_rod = 0xDAA520,               // rgb(218,165,32)
 | 
			
		||||
  gray = 0x808080,                     // rgb(128,128,128)
 | 
			
		||||
  green = 0x008000,                    // rgb(0,128,0)
 | 
			
		||||
  green_yellow = 0xADFF2F,             // rgb(173,255,47)
 | 
			
		||||
  honey_dew = 0xF0FFF0,                // rgb(240,255,240)
 | 
			
		||||
  hot_pink = 0xFF69B4,                 // rgb(255,105,180)
 | 
			
		||||
  indian_red = 0xCD5C5C,               // rgb(205,92,92)
 | 
			
		||||
  indigo = 0x4B0082,                   // rgb(75,0,130)
 | 
			
		||||
  ivory = 0xFFFFF0,                    // rgb(255,255,240)
 | 
			
		||||
  khaki = 0xF0E68C,                    // rgb(240,230,140)
 | 
			
		||||
  lavender = 0xE6E6FA,                 // rgb(230,230,250)
 | 
			
		||||
  lavender_blush = 0xFFF0F5,           // rgb(255,240,245)
 | 
			
		||||
  lawn_green = 0x7CFC00,               // rgb(124,252,0)
 | 
			
		||||
  lemon_chiffon = 0xFFFACD,            // rgb(255,250,205)
 | 
			
		||||
  light_blue = 0xADD8E6,               // rgb(173,216,230)
 | 
			
		||||
  light_coral = 0xF08080,              // rgb(240,128,128)
 | 
			
		||||
  light_cyan = 0xE0FFFF,               // rgb(224,255,255)
 | 
			
		||||
  light_golden_rod_yellow = 0xFAFAD2,  // rgb(250,250,210)
 | 
			
		||||
  light_gray = 0xD3D3D3,               // rgb(211,211,211)
 | 
			
		||||
  light_green = 0x90EE90,              // rgb(144,238,144)
 | 
			
		||||
  light_pink = 0xFFB6C1,               // rgb(255,182,193)
 | 
			
		||||
  light_salmon = 0xFFA07A,             // rgb(255,160,122)
 | 
			
		||||
  light_sea_green = 0x20B2AA,          // rgb(32,178,170)
 | 
			
		||||
  light_sky_blue = 0x87CEFA,           // rgb(135,206,250)
 | 
			
		||||
  light_slate_gray = 0x778899,         // rgb(119,136,153)
 | 
			
		||||
  light_steel_blue = 0xB0C4DE,         // rgb(176,196,222)
 | 
			
		||||
  light_yellow = 0xFFFFE0,             // rgb(255,255,224)
 | 
			
		||||
  lime = 0x00FF00,                     // rgb(0,255,0)
 | 
			
		||||
  lime_green = 0x32CD32,               // rgb(50,205,50)
 | 
			
		||||
  linen = 0xFAF0E6,                    // rgb(250,240,230)
 | 
			
		||||
  magenta = 0xFF00FF,                  // rgb(255,0,255)
 | 
			
		||||
  maroon = 0x800000,                   // rgb(128,0,0)
 | 
			
		||||
  medium_aquamarine = 0x66CDAA,        // rgb(102,205,170)
 | 
			
		||||
  medium_blue = 0x0000CD,              // rgb(0,0,205)
 | 
			
		||||
  medium_orchid = 0xBA55D3,            // rgb(186,85,211)
 | 
			
		||||
  medium_purple = 0x9370DB,            // rgb(147,112,219)
 | 
			
		||||
  medium_sea_green = 0x3CB371,         // rgb(60,179,113)
 | 
			
		||||
  medium_slate_blue = 0x7B68EE,        // rgb(123,104,238)
 | 
			
		||||
  medium_spring_green = 0x00FA9A,      // rgb(0,250,154)
 | 
			
		||||
  medium_turquoise = 0x48D1CC,         // rgb(72,209,204)
 | 
			
		||||
  medium_violet_red = 0xC71585,        // rgb(199,21,133)
 | 
			
		||||
  midnight_blue = 0x191970,            // rgb(25,25,112)
 | 
			
		||||
  mint_cream = 0xF5FFFA,               // rgb(245,255,250)
 | 
			
		||||
  misty_rose = 0xFFE4E1,               // rgb(255,228,225)
 | 
			
		||||
  moccasin = 0xFFE4B5,                 // rgb(255,228,181)
 | 
			
		||||
  navajo_white = 0xFFDEAD,             // rgb(255,222,173)
 | 
			
		||||
  navy = 0x000080,                     // rgb(0,0,128)
 | 
			
		||||
  old_lace = 0xFDF5E6,                 // rgb(253,245,230)
 | 
			
		||||
  olive = 0x808000,                    // rgb(128,128,0)
 | 
			
		||||
  olive_drab = 0x6B8E23,               // rgb(107,142,35)
 | 
			
		||||
  orange = 0xFFA500,                   // rgb(255,165,0)
 | 
			
		||||
  orange_red = 0xFF4500,               // rgb(255,69,0)
 | 
			
		||||
  orchid = 0xDA70D6,                   // rgb(218,112,214)
 | 
			
		||||
  pale_golden_rod = 0xEEE8AA,          // rgb(238,232,170)
 | 
			
		||||
  pale_green = 0x98FB98,               // rgb(152,251,152)
 | 
			
		||||
  pale_turquoise = 0xAFEEEE,           // rgb(175,238,238)
 | 
			
		||||
  pale_violet_red = 0xDB7093,          // rgb(219,112,147)
 | 
			
		||||
  papaya_whip = 0xFFEFD5,              // rgb(255,239,213)
 | 
			
		||||
  peach_puff = 0xFFDAB9,               // rgb(255,218,185)
 | 
			
		||||
  peru = 0xCD853F,                     // rgb(205,133,63)
 | 
			
		||||
  pink = 0xFFC0CB,                     // rgb(255,192,203)
 | 
			
		||||
  plum = 0xDDA0DD,                     // rgb(221,160,221)
 | 
			
		||||
  powder_blue = 0xB0E0E6,              // rgb(176,224,230)
 | 
			
		||||
  purple = 0x800080,                   // rgb(128,0,128)
 | 
			
		||||
  rebecca_purple = 0x663399,           // rgb(102,51,153)
 | 
			
		||||
  red = 0xFF0000,                      // rgb(255,0,0)
 | 
			
		||||
  rosy_brown = 0xBC8F8F,               // rgb(188,143,143)
 | 
			
		||||
  royal_blue = 0x4169E1,               // rgb(65,105,225)
 | 
			
		||||
  saddle_brown = 0x8B4513,             // rgb(139,69,19)
 | 
			
		||||
  salmon = 0xFA8072,                   // rgb(250,128,114)
 | 
			
		||||
  sandy_brown = 0xF4A460,              // rgb(244,164,96)
 | 
			
		||||
  sea_green = 0x2E8B57,                // rgb(46,139,87)
 | 
			
		||||
  sea_shell = 0xFFF5EE,                // rgb(255,245,238)
 | 
			
		||||
  sienna = 0xA0522D,                   // rgb(160,82,45)
 | 
			
		||||
  silver = 0xC0C0C0,                   // rgb(192,192,192)
 | 
			
		||||
  sky_blue = 0x87CEEB,                 // rgb(135,206,235)
 | 
			
		||||
  slate_blue = 0x6A5ACD,               // rgb(106,90,205)
 | 
			
		||||
  slate_gray = 0x708090,               // rgb(112,128,144)
 | 
			
		||||
  snow = 0xFFFAFA,                     // rgb(255,250,250)
 | 
			
		||||
  spring_green = 0x00FF7F,             // rgb(0,255,127)
 | 
			
		||||
  steel_blue = 0x4682B4,               // rgb(70,130,180)
 | 
			
		||||
  tan = 0xD2B48C,                      // rgb(210,180,140)
 | 
			
		||||
  teal = 0x008080,                     // rgb(0,128,128)
 | 
			
		||||
  thistle = 0xD8BFD8,                  // rgb(216,191,216)
 | 
			
		||||
  tomato = 0xFF6347,                   // rgb(255,99,71)
 | 
			
		||||
  turquoise = 0x40E0D0,                // rgb(64,224,208)
 | 
			
		||||
  violet = 0xEE82EE,                   // rgb(238,130,238)
 | 
			
		||||
  wheat = 0xF5DEB3,                    // rgb(245,222,179)
 | 
			
		||||
  white = 0xFFFFFF,                    // rgb(255,255,255)
 | 
			
		||||
  white_smoke = 0xF5F5F5,              // rgb(245,245,245)
 | 
			
		||||
  yellow = 0xFFFF00,                   // rgb(255,255,0)
 | 
			
		||||
  yellow_green = 0x9ACD32              // rgb(154,205,50)
 | 
			
		||||
};                                     // enum class color
 | 
			
		||||
 | 
			
		||||
enum class terminal_color : uint8_t {
 | 
			
		||||
  black = 30,
 | 
			
		||||
  red,
 | 
			
		||||
  green,
 | 
			
		||||
  yellow,
 | 
			
		||||
  blue,
 | 
			
		||||
  magenta,
 | 
			
		||||
  cyan,
 | 
			
		||||
  white,
 | 
			
		||||
  bright_black = 90,
 | 
			
		||||
  bright_red,
 | 
			
		||||
  bright_green,
 | 
			
		||||
  bright_yellow,
 | 
			
		||||
  bright_blue,
 | 
			
		||||
  bright_magenta,
 | 
			
		||||
  bright_cyan,
 | 
			
		||||
  bright_white
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class emphasis : uint8_t {
 | 
			
		||||
  bold = 1,
 | 
			
		||||
  faint = 1 << 1,
 | 
			
		||||
  italic = 1 << 2,
 | 
			
		||||
  underline = 1 << 3,
 | 
			
		||||
  blink = 1 << 4,
 | 
			
		||||
  reverse = 1 << 5,
 | 
			
		||||
  conceal = 1 << 6,
 | 
			
		||||
  strikethrough = 1 << 7,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// rgb is a struct for red, green and blue colors.
 | 
			
		||||
// Using the name "rgb" makes some editors show the color in a tooltip.
 | 
			
		||||
struct rgb {
 | 
			
		||||
  FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
 | 
			
		||||
  FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
 | 
			
		||||
  FMT_CONSTEXPR rgb(uint32_t hex)
 | 
			
		||||
      : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
 | 
			
		||||
  FMT_CONSTEXPR rgb(color hex)
 | 
			
		||||
      : r((uint32_t(hex) >> 16) & 0xFF),
 | 
			
		||||
        g((uint32_t(hex) >> 8) & 0xFF),
 | 
			
		||||
        b(uint32_t(hex) & 0xFF) {}
 | 
			
		||||
  uint8_t r;
 | 
			
		||||
  uint8_t g;
 | 
			
		||||
  uint8_t b;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
// color is a struct of either a rgb color or a terminal color.
 | 
			
		||||
struct color_type {
 | 
			
		||||
  FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
 | 
			
		||||
  FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
 | 
			
		||||
    value.rgb_color = static_cast<uint32_t>(rgb_color);
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
 | 
			
		||||
    value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
 | 
			
		||||
                      (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
 | 
			
		||||
      : is_rgb(), value{} {
 | 
			
		||||
    value.term_color = static_cast<uint8_t>(term_color);
 | 
			
		||||
  }
 | 
			
		||||
  bool is_rgb;
 | 
			
		||||
  union color_union {
 | 
			
		||||
    uint8_t term_color;
 | 
			
		||||
    uint32_t rgb_color;
 | 
			
		||||
  } value;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
/// A text style consisting of foreground and background colors and emphasis.
 | 
			
		||||
class text_style {
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
 | 
			
		||||
      : set_foreground_color(), set_background_color(), ems(em) {}
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
 | 
			
		||||
    if (!set_foreground_color) {
 | 
			
		||||
      set_foreground_color = rhs.set_foreground_color;
 | 
			
		||||
      foreground_color = rhs.foreground_color;
 | 
			
		||||
    } else if (rhs.set_foreground_color) {
 | 
			
		||||
      if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
 | 
			
		||||
        report_error("can't OR a terminal color");
 | 
			
		||||
      foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!set_background_color) {
 | 
			
		||||
      set_background_color = rhs.set_background_color;
 | 
			
		||||
      background_color = rhs.background_color;
 | 
			
		||||
    } else if (rhs.set_background_color) {
 | 
			
		||||
      if (!background_color.is_rgb || !rhs.background_color.is_rgb)
 | 
			
		||||
        report_error("can't OR a terminal color");
 | 
			
		||||
      background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
 | 
			
		||||
                                static_cast<uint8_t>(rhs.ems));
 | 
			
		||||
    return *this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
 | 
			
		||||
      -> text_style {
 | 
			
		||||
    return lhs |= rhs;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
 | 
			
		||||
    return set_foreground_color;
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR auto has_background() const noexcept -> bool {
 | 
			
		||||
    return set_background_color;
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
 | 
			
		||||
    return static_cast<uint8_t>(ems) != 0;
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
 | 
			
		||||
    FMT_ASSERT(has_foreground(), "no foreground specified for this style");
 | 
			
		||||
    return foreground_color;
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
 | 
			
		||||
    FMT_ASSERT(has_background(), "no background specified for this style");
 | 
			
		||||
    return background_color;
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
 | 
			
		||||
    FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
 | 
			
		||||
    return ems;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  FMT_CONSTEXPR text_style(bool is_foreground,
 | 
			
		||||
                           detail::color_type text_color) noexcept
 | 
			
		||||
      : set_foreground_color(), set_background_color(), ems() {
 | 
			
		||||
    if (is_foreground) {
 | 
			
		||||
      foreground_color = text_color;
 | 
			
		||||
      set_foreground_color = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      background_color = text_color;
 | 
			
		||||
      set_background_color = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
 | 
			
		||||
      -> text_style;
 | 
			
		||||
 | 
			
		||||
  friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
 | 
			
		||||
      -> text_style;
 | 
			
		||||
 | 
			
		||||
  detail::color_type foreground_color;
 | 
			
		||||
  detail::color_type background_color;
 | 
			
		||||
  bool set_foreground_color;
 | 
			
		||||
  bool set_background_color;
 | 
			
		||||
  emphasis ems;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Creates a text style from the foreground (text) color.
 | 
			
		||||
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
 | 
			
		||||
    -> text_style {
 | 
			
		||||
  return text_style(true, foreground);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Creates a text style from the background color.
 | 
			
		||||
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
 | 
			
		||||
    -> text_style {
 | 
			
		||||
  return text_style(false, background);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
 | 
			
		||||
    -> text_style {
 | 
			
		||||
  return text_style(lhs) | rhs;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename Char> struct ansi_color_escape {
 | 
			
		||||
  FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
 | 
			
		||||
                                  const char* esc) noexcept {
 | 
			
		||||
    // If we have a terminal color, we need to output another escape code
 | 
			
		||||
    // sequence.
 | 
			
		||||
    if (!text_color.is_rgb) {
 | 
			
		||||
      bool is_background = esc == string_view("\x1b[48;2;");
 | 
			
		||||
      uint32_t value = text_color.value.term_color;
 | 
			
		||||
      // Background ASCII codes are the same as the foreground ones but with
 | 
			
		||||
      // 10 more.
 | 
			
		||||
      if (is_background) value += 10u;
 | 
			
		||||
 | 
			
		||||
      size_t index = 0;
 | 
			
		||||
      buffer[index++] = static_cast<Char>('\x1b');
 | 
			
		||||
      buffer[index++] = static_cast<Char>('[');
 | 
			
		||||
 | 
			
		||||
      if (value >= 100u) {
 | 
			
		||||
        buffer[index++] = static_cast<Char>('1');
 | 
			
		||||
        value %= 100u;
 | 
			
		||||
      }
 | 
			
		||||
      buffer[index++] = static_cast<Char>('0' + value / 10u);
 | 
			
		||||
      buffer[index++] = static_cast<Char>('0' + value % 10u);
 | 
			
		||||
 | 
			
		||||
      buffer[index++] = static_cast<Char>('m');
 | 
			
		||||
      buffer[index++] = static_cast<Char>('\0');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < 7; i++) {
 | 
			
		||||
      buffer[i] = static_cast<Char>(esc[i]);
 | 
			
		||||
    }
 | 
			
		||||
    rgb color(text_color.value.rgb_color);
 | 
			
		||||
    to_esc(color.r, buffer + 7, ';');
 | 
			
		||||
    to_esc(color.g, buffer + 11, ';');
 | 
			
		||||
    to_esc(color.b, buffer + 15, 'm');
 | 
			
		||||
    buffer[19] = static_cast<Char>(0);
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
 | 
			
		||||
    uint8_t em_codes[num_emphases] = {};
 | 
			
		||||
    if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
 | 
			
		||||
    if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
 | 
			
		||||
    if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
 | 
			
		||||
    if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
 | 
			
		||||
    if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
 | 
			
		||||
    if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
 | 
			
		||||
    if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
 | 
			
		||||
    if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
 | 
			
		||||
 | 
			
		||||
    size_t index = 0;
 | 
			
		||||
    for (size_t i = 0; i < num_emphases; ++i) {
 | 
			
		||||
      if (!em_codes[i]) continue;
 | 
			
		||||
      buffer[index++] = static_cast<Char>('\x1b');
 | 
			
		||||
      buffer[index++] = static_cast<Char>('[');
 | 
			
		||||
      buffer[index++] = static_cast<Char>('0' + em_codes[i]);
 | 
			
		||||
      buffer[index++] = static_cast<Char>('m');
 | 
			
		||||
    }
 | 
			
		||||
    buffer[index++] = static_cast<Char>(0);
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
 | 
			
		||||
  FMT_CONSTEXPR20 auto end() const noexcept -> const Char* {
 | 
			
		||||
    return buffer + basic_string_view<Char>(buffer).size();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  static constexpr size_t num_emphases = 8;
 | 
			
		||||
  Char buffer[7u + 3u * num_emphases + 1u];
 | 
			
		||||
 | 
			
		||||
  static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
 | 
			
		||||
                                   char delimiter) noexcept {
 | 
			
		||||
    out[0] = static_cast<Char>('0' + c / 100);
 | 
			
		||||
    out[1] = static_cast<Char>('0' + c / 10 % 10);
 | 
			
		||||
    out[2] = static_cast<Char>('0' + c % 10);
 | 
			
		||||
    out[3] = static_cast<Char>(delimiter);
 | 
			
		||||
  }
 | 
			
		||||
  static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
 | 
			
		||||
      -> bool {
 | 
			
		||||
    return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
 | 
			
		||||
    -> ansi_color_escape<Char> {
 | 
			
		||||
  return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
 | 
			
		||||
    -> ansi_color_escape<Char> {
 | 
			
		||||
  return ansi_color_escape<Char>(background, "\x1b[48;2;");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept
 | 
			
		||||
    -> ansi_color_escape<Char> {
 | 
			
		||||
  return ansi_color_escape<Char>(em);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
 | 
			
		||||
  auto reset_color = string_view("\x1b[0m");
 | 
			
		||||
  buffer.append(reset_color.begin(), reset_color.end());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T> struct styled_arg : detail::view {
 | 
			
		||||
  const T& value;
 | 
			
		||||
  text_style style;
 | 
			
		||||
  styled_arg(const T& v, text_style s) : value(v), style(s) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
void vformat_to(
 | 
			
		||||
    buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str,
 | 
			
		||||
    basic_format_args<buffered_context<type_identity_t<Char>>> args) {
 | 
			
		||||
  bool has_style = false;
 | 
			
		||||
  if (ts.has_emphasis()) {
 | 
			
		||||
    has_style = true;
 | 
			
		||||
    auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
 | 
			
		||||
    buf.append(emphasis.begin(), emphasis.end());
 | 
			
		||||
  }
 | 
			
		||||
  if (ts.has_foreground()) {
 | 
			
		||||
    has_style = true;
 | 
			
		||||
    auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
 | 
			
		||||
    buf.append(foreground.begin(), foreground.end());
 | 
			
		||||
  }
 | 
			
		||||
  if (ts.has_background()) {
 | 
			
		||||
    has_style = true;
 | 
			
		||||
    auto background = detail::make_background_color<Char>(ts.get_background());
 | 
			
		||||
    buf.append(background.begin(), background.end());
 | 
			
		||||
  }
 | 
			
		||||
  detail::vformat_to(buf, format_str, args, {});
 | 
			
		||||
  if (has_style) detail::reset_color<Char>(buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
inline void vprint(FILE* f, const text_style& ts, string_view fmt,
 | 
			
		||||
                   format_args args) {
 | 
			
		||||
  auto buf = memory_buffer();
 | 
			
		||||
  detail::vformat_to(buf, ts, fmt, args);
 | 
			
		||||
  print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats a string and prints it to the specified file stream using ANSI
 | 
			
		||||
 * escape sequences to specify text formatting.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
 | 
			
		||||
 *                "Elapsed time: {0:.2f} seconds", 1.23);
 | 
			
		||||
 */
 | 
			
		||||
template <typename... T>
 | 
			
		||||
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
 | 
			
		||||
           T&&... args) {
 | 
			
		||||
  vprint(f, ts, fmt, fmt::make_format_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats a string and prints it to stdout using ANSI escape sequences to
 | 
			
		||||
 * specify text formatting.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
 | 
			
		||||
 *                "Elapsed time: {0:.2f} seconds", 1.23);
 | 
			
		||||
 */
 | 
			
		||||
template <typename... T>
 | 
			
		||||
void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
 | 
			
		||||
  return print(stdout, ts, fmt, std::forward<T>(args)...);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline auto vformat(const text_style& ts, string_view fmt, format_args args)
 | 
			
		||||
    -> std::string {
 | 
			
		||||
  auto buf = memory_buffer();
 | 
			
		||||
  detail::vformat_to(buf, ts, fmt, args);
 | 
			
		||||
  return fmt::to_string(buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats arguments and returns the result as a string using ANSI escape
 | 
			
		||||
 * sequences to specify text formatting.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 * ```
 | 
			
		||||
 * #include <fmt/color.h>
 | 
			
		||||
 * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
 | 
			
		||||
 *                                   "The answer is {}", 42);
 | 
			
		||||
 * ```
 | 
			
		||||
 */
 | 
			
		||||
template <typename... T>
 | 
			
		||||
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
 | 
			
		||||
    -> std::string {
 | 
			
		||||
  return fmt::vformat(ts, fmt, fmt::make_format_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Formats a string with the given text_style and writes the output to `out`.
 | 
			
		||||
template <typename OutputIt,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
 | 
			
		||||
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
 | 
			
		||||
                format_args args) -> OutputIt {
 | 
			
		||||
  auto&& buf = detail::get_buffer<char>(out);
 | 
			
		||||
  detail::vformat_to(buf, ts, fmt, args);
 | 
			
		||||
  return detail::get_iterator(buf, out);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats arguments with the given text style, writes the result to the output
 | 
			
		||||
 * iterator `out` and returns the iterator past the end of the output range.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     std::vector<char> out;
 | 
			
		||||
 *     fmt::format_to(std::back_inserter(out),
 | 
			
		||||
 *                    fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
 | 
			
		||||
 */
 | 
			
		||||
template <typename OutputIt, typename... T,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
 | 
			
		||||
inline auto format_to(OutputIt out, const text_style& ts,
 | 
			
		||||
                      format_string<T...> fmt, T&&... args) -> OutputIt {
 | 
			
		||||
  return vformat_to(out, ts, fmt, fmt::make_format_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Char>
 | 
			
		||||
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    const auto& ts = arg.style;
 | 
			
		||||
    const auto& value = arg.value;
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
 | 
			
		||||
    bool has_style = false;
 | 
			
		||||
    if (ts.has_emphasis()) {
 | 
			
		||||
      has_style = true;
 | 
			
		||||
      auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
 | 
			
		||||
      out = std::copy(emphasis.begin(), emphasis.end(), out);
 | 
			
		||||
    }
 | 
			
		||||
    if (ts.has_foreground()) {
 | 
			
		||||
      has_style = true;
 | 
			
		||||
      auto foreground =
 | 
			
		||||
          detail::make_foreground_color<Char>(ts.get_foreground());
 | 
			
		||||
      out = std::copy(foreground.begin(), foreground.end(), out);
 | 
			
		||||
    }
 | 
			
		||||
    if (ts.has_background()) {
 | 
			
		||||
      has_style = true;
 | 
			
		||||
      auto background =
 | 
			
		||||
          detail::make_background_color<Char>(ts.get_background());
 | 
			
		||||
      out = std::copy(background.begin(), background.end(), out);
 | 
			
		||||
    }
 | 
			
		||||
    out = formatter<T, Char>::format(value, ctx);
 | 
			
		||||
    if (has_style) {
 | 
			
		||||
      auto reset_color = string_view("\x1b[0m");
 | 
			
		||||
      out = std::copy(reset_color.begin(), reset_color.end(), out);
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns an argument that will be formatted using ANSI escape sequences,
 | 
			
		||||
 * to be used in a formatting function.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     fmt::print("Elapsed time: {0:.2f} seconds",
 | 
			
		||||
 *                fmt::styled(1.23, fmt::fg(fmt::color::green) |
 | 
			
		||||
 *                                  fmt::bg(fmt::color::blue)));
 | 
			
		||||
 */
 | 
			
		||||
template <typename T>
 | 
			
		||||
FMT_CONSTEXPR auto styled(const T& value, text_style ts)
 | 
			
		||||
    -> detail::styled_arg<remove_cvref_t<T>> {
 | 
			
		||||
  return detail::styled_arg<remove_cvref_t<T>>{value, ts};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_END_EXPORT
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#endif  // FMT_COLOR_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,529 @@
 | 
			
		|||
// Formatting library for C++ - experimental format string compilation
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_COMPILE_H_
 | 
			
		||||
#define FMT_COMPILE_H_
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <iterator>  // std::back_inserter
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "format.h"
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
 | 
			
		||||
// A compile-time string which is compiled into fast formatting code.
 | 
			
		||||
FMT_EXPORT class compiled_string {};
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename T, typename InputIt>
 | 
			
		||||
FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it)
 | 
			
		||||
    -> counting_iterator {
 | 
			
		||||
  return it + (end - begin);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename S>
 | 
			
		||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Converts a string literal `s` into a format string that will be parsed at
 | 
			
		||||
 * compile time and converted into efficient formatting code. Requires C++17
 | 
			
		||||
 * `constexpr if` compiler support.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     // Converts 42 into std::string using the most efficient method and no
 | 
			
		||||
 *     // runtime format string processing.
 | 
			
		||||
 *     std::string s = fmt::format(FMT_COMPILE("{}"), 42);
 | 
			
		||||
 */
 | 
			
		||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
 | 
			
		||||
#  define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit)
 | 
			
		||||
#else
 | 
			
		||||
#  define FMT_COMPILE(s) FMT_STRING(s)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
 | 
			
		||||
template <typename Char, size_t N,
 | 
			
		||||
          fmt::detail_exported::fixed_string<Char, N> Str>
 | 
			
		||||
struct udl_compiled_string : compiled_string {
 | 
			
		||||
  using char_type = Char;
 | 
			
		||||
  explicit constexpr operator basic_string_view<char_type>() const {
 | 
			
		||||
    return {Str.data, N - 1};
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
template <typename T, typename... Tail>
 | 
			
		||||
auto first(const T& value, const Tail&...) -> const T& {
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
 | 
			
		||||
template <typename... Args> struct type_list {};
 | 
			
		||||
 | 
			
		||||
// Returns a reference to the argument at index N from [first, rest...].
 | 
			
		||||
template <int N, typename T, typename... Args>
 | 
			
		||||
constexpr const auto& get([[maybe_unused]] const T& first,
 | 
			
		||||
                          [[maybe_unused]] const Args&... rest) {
 | 
			
		||||
  static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
 | 
			
		||||
  if constexpr (N == 0)
 | 
			
		||||
    return first;
 | 
			
		||||
  else
 | 
			
		||||
    return detail::get<N - 1>(rest...);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename... Args>
 | 
			
		||||
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
 | 
			
		||||
                                    type_list<Args...>) {
 | 
			
		||||
  return get_arg_index_by_name<Args...>(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <int N, typename> struct get_type_impl;
 | 
			
		||||
 | 
			
		||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
 | 
			
		||||
  using type =
 | 
			
		||||
      remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <int N, typename T>
 | 
			
		||||
using get_type = typename get_type_impl<N, T>::type;
 | 
			
		||||
 | 
			
		||||
template <typename T> struct is_compiled_format : std::false_type {};
 | 
			
		||||
 | 
			
		||||
template <typename Char> struct text {
 | 
			
		||||
  basic_string_view<Char> data;
 | 
			
		||||
  using char_type = Char;
 | 
			
		||||
 | 
			
		||||
  template <typename OutputIt, typename... Args>
 | 
			
		||||
  constexpr OutputIt format(OutputIt out, const Args&...) const {
 | 
			
		||||
    return write<Char>(out, data);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
struct is_compiled_format<text<Char>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
 | 
			
		||||
                               size_t size) {
 | 
			
		||||
  return {{&s[pos], size}};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char> struct code_unit {
 | 
			
		||||
  Char value;
 | 
			
		||||
  using char_type = Char;
 | 
			
		||||
 | 
			
		||||
  template <typename OutputIt, typename... Args>
 | 
			
		||||
  constexpr OutputIt format(OutputIt out, const Args&...) const {
 | 
			
		||||
    *out++ = value;
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// This ensures that the argument type is convertible to `const T&`.
 | 
			
		||||
template <typename T, int N, typename... Args>
 | 
			
		||||
constexpr const T& get_arg_checked(const Args&... args) {
 | 
			
		||||
  const auto& arg = detail::get<N>(args...);
 | 
			
		||||
  if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
 | 
			
		||||
    return arg.value;
 | 
			
		||||
  } else {
 | 
			
		||||
    return arg;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
struct is_compiled_format<code_unit<Char>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
// A replacement field that refers to argument N.
 | 
			
		||||
template <typename Char, typename T, int N> struct field {
 | 
			
		||||
  using char_type = Char;
 | 
			
		||||
 | 
			
		||||
  template <typename OutputIt, typename... Args>
 | 
			
		||||
  constexpr OutputIt format(OutputIt out, const Args&... args) const {
 | 
			
		||||
    const T& arg = get_arg_checked<T, N>(args...);
 | 
			
		||||
    if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) {
 | 
			
		||||
      auto s = basic_string_view<Char>(arg);
 | 
			
		||||
      return copy<Char>(s.begin(), s.end(), out);
 | 
			
		||||
    }
 | 
			
		||||
    return write<Char>(out, arg);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename T, int N>
 | 
			
		||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
// A replacement field that refers to argument with name.
 | 
			
		||||
template <typename Char> struct runtime_named_field {
 | 
			
		||||
  using char_type = Char;
 | 
			
		||||
  basic_string_view<Char> name;
 | 
			
		||||
 | 
			
		||||
  template <typename OutputIt, typename T>
 | 
			
		||||
  constexpr static bool try_format_argument(
 | 
			
		||||
      OutputIt& out,
 | 
			
		||||
      // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
 | 
			
		||||
      [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
 | 
			
		||||
    if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
 | 
			
		||||
      if (arg_name == arg.name) {
 | 
			
		||||
        out = write<Char>(out, arg.value);
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename OutputIt, typename... Args>
 | 
			
		||||
  constexpr OutputIt format(OutputIt out, const Args&... args) const {
 | 
			
		||||
    bool found = (try_format_argument(out, name, args) || ...);
 | 
			
		||||
    if (!found) {
 | 
			
		||||
      FMT_THROW(format_error("argument with specified name is not found"));
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
// A replacement field that refers to argument N and has format specifiers.
 | 
			
		||||
template <typename Char, typename T, int N> struct spec_field {
 | 
			
		||||
  using char_type = Char;
 | 
			
		||||
  formatter<T, Char> fmt;
 | 
			
		||||
 | 
			
		||||
  template <typename OutputIt, typename... Args>
 | 
			
		||||
  constexpr FMT_INLINE OutputIt format(OutputIt out,
 | 
			
		||||
                                       const Args&... args) const {
 | 
			
		||||
    const auto& vargs =
 | 
			
		||||
        fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
 | 
			
		||||
    basic_format_context<OutputIt, Char> ctx(out, vargs);
 | 
			
		||||
    return fmt.format(get_arg_checked<T, N>(args...), ctx);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename T, int N>
 | 
			
		||||
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
template <typename L, typename R> struct concat {
 | 
			
		||||
  L lhs;
 | 
			
		||||
  R rhs;
 | 
			
		||||
  using char_type = typename L::char_type;
 | 
			
		||||
 | 
			
		||||
  template <typename OutputIt, typename... Args>
 | 
			
		||||
  constexpr OutputIt format(OutputIt out, const Args&... args) const {
 | 
			
		||||
    out = lhs.format(out, args...);
 | 
			
		||||
    return rhs.format(out, args...);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename L, typename R>
 | 
			
		||||
struct is_compiled_format<concat<L, R>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
template <typename L, typename R>
 | 
			
		||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
 | 
			
		||||
  return {lhs, rhs};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct unknown_format {};
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
 | 
			
		||||
  for (size_t size = str.size(); pos != size; ++pos) {
 | 
			
		||||
    if (str[pos] == '{' || str[pos] == '}') break;
 | 
			
		||||
  }
 | 
			
		||||
  return pos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Args, size_t POS, int ID, typename S>
 | 
			
		||||
constexpr auto compile_format_string(S fmt);
 | 
			
		||||
 | 
			
		||||
template <typename Args, size_t POS, int ID, typename T, typename S>
 | 
			
		||||
constexpr auto parse_tail(T head, S fmt) {
 | 
			
		||||
  if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {
 | 
			
		||||
    constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);
 | 
			
		||||
    if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
 | 
			
		||||
                               unknown_format>())
 | 
			
		||||
      return tail;
 | 
			
		||||
    else
 | 
			
		||||
      return make_concat(head, tail);
 | 
			
		||||
  } else {
 | 
			
		||||
    return head;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Char> struct parse_specs_result {
 | 
			
		||||
  formatter<T, Char> fmt;
 | 
			
		||||
  size_t end;
 | 
			
		||||
  int next_arg_id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum { manual_indexing_id = -1 };
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Char>
 | 
			
		||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
 | 
			
		||||
                                                  size_t pos, int next_arg_id) {
 | 
			
		||||
  str.remove_prefix(pos);
 | 
			
		||||
  auto ctx =
 | 
			
		||||
      compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
 | 
			
		||||
  auto f = formatter<T, Char>();
 | 
			
		||||
  auto end = f.parse(ctx);
 | 
			
		||||
  return {f, pos + fmt::detail::to_unsigned(end - str.data()),
 | 
			
		||||
          next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char> struct arg_id_handler {
 | 
			
		||||
  arg_ref<Char> arg_id;
 | 
			
		||||
 | 
			
		||||
  constexpr int on_auto() {
 | 
			
		||||
    FMT_ASSERT(false, "handler cannot be used with automatic indexing");
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  constexpr int on_index(int id) {
 | 
			
		||||
    arg_id = arg_ref<Char>(id);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  constexpr int on_name(basic_string_view<Char> id) {
 | 
			
		||||
    arg_id = arg_ref<Char>(id);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char> struct parse_arg_id_result {
 | 
			
		||||
  arg_ref<Char> arg_id;
 | 
			
		||||
  const Char* arg_id_end;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <int ID, typename Char>
 | 
			
		||||
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
 | 
			
		||||
  auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
 | 
			
		||||
  auto arg_id_end = parse_arg_id(begin, end, handler);
 | 
			
		||||
  return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Enable = void> struct field_type {
 | 
			
		||||
  using type = remove_cvref_t<T>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
 | 
			
		||||
  using type = remove_cvref_t<decltype(T::value)>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
 | 
			
		||||
          typename S>
 | 
			
		||||
constexpr auto parse_replacement_field_then_tail(S fmt) {
 | 
			
		||||
  using char_type = typename S::char_type;
 | 
			
		||||
  constexpr auto str = basic_string_view<char_type>(fmt);
 | 
			
		||||
  constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
 | 
			
		||||
  if constexpr (c == '}') {
 | 
			
		||||
    return parse_tail<Args, END_POS + 1, NEXT_ID>(
 | 
			
		||||
        field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);
 | 
			
		||||
  } else if constexpr (c != ':') {
 | 
			
		||||
    FMT_THROW(format_error("expected ':'"));
 | 
			
		||||
  } else {
 | 
			
		||||
    constexpr auto result = parse_specs<typename field_type<T>::type>(
 | 
			
		||||
        str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
 | 
			
		||||
    if constexpr (result.end >= str.size() || str[result.end] != '}') {
 | 
			
		||||
      FMT_THROW(format_error("expected '}'"));
 | 
			
		||||
      return 0;
 | 
			
		||||
    } else {
 | 
			
		||||
      return parse_tail<Args, result.end + 1, result.next_arg_id>(
 | 
			
		||||
          spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
 | 
			
		||||
              result.fmt},
 | 
			
		||||
          fmt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Compiles a non-empty format string and returns the compiled representation
 | 
			
		||||
// or unknown_format() on unrecognized input.
 | 
			
		||||
template <typename Args, size_t POS, int ID, typename S>
 | 
			
		||||
constexpr auto compile_format_string(S fmt) {
 | 
			
		||||
  using char_type = typename S::char_type;
 | 
			
		||||
  constexpr auto str = basic_string_view<char_type>(fmt);
 | 
			
		||||
  if constexpr (str[POS] == '{') {
 | 
			
		||||
    if constexpr (POS + 1 == str.size())
 | 
			
		||||
      FMT_THROW(format_error("unmatched '{' in format string"));
 | 
			
		||||
    if constexpr (str[POS + 1] == '{') {
 | 
			
		||||
      return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
 | 
			
		||||
    } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
 | 
			
		||||
      static_assert(ID != manual_indexing_id,
 | 
			
		||||
                    "cannot switch from manual to automatic argument indexing");
 | 
			
		||||
      constexpr auto next_id =
 | 
			
		||||
          ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
 | 
			
		||||
      return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
 | 
			
		||||
                                               POS + 1, ID, next_id>(fmt);
 | 
			
		||||
    } else {
 | 
			
		||||
      constexpr auto arg_id_result =
 | 
			
		||||
          parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
 | 
			
		||||
      constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
 | 
			
		||||
      constexpr char_type c =
 | 
			
		||||
          arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
 | 
			
		||||
      static_assert(c == '}' || c == ':', "missing '}' in format string");
 | 
			
		||||
      if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
 | 
			
		||||
        static_assert(
 | 
			
		||||
            ID == manual_indexing_id || ID == 0,
 | 
			
		||||
            "cannot switch from automatic to manual argument indexing");
 | 
			
		||||
        constexpr auto arg_index = arg_id_result.arg_id.val.index;
 | 
			
		||||
        return parse_replacement_field_then_tail<get_type<arg_index, Args>,
 | 
			
		||||
                                                 Args, arg_id_end_pos,
 | 
			
		||||
                                                 arg_index, manual_indexing_id>(
 | 
			
		||||
            fmt);
 | 
			
		||||
      } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
 | 
			
		||||
        constexpr auto arg_index =
 | 
			
		||||
            get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
 | 
			
		||||
        if constexpr (arg_index >= 0) {
 | 
			
		||||
          constexpr auto next_id =
 | 
			
		||||
              ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
 | 
			
		||||
          return parse_replacement_field_then_tail<
 | 
			
		||||
              decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
 | 
			
		||||
              arg_index, next_id>(fmt);
 | 
			
		||||
        } else if constexpr (c == '}') {
 | 
			
		||||
          return parse_tail<Args, arg_id_end_pos + 1, ID>(
 | 
			
		||||
              runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
 | 
			
		||||
              fmt);
 | 
			
		||||
        } else if constexpr (c == ':') {
 | 
			
		||||
          return unknown_format();  // no type info for specs parsing
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else if constexpr (str[POS] == '}') {
 | 
			
		||||
    if constexpr (POS + 1 == str.size())
 | 
			
		||||
      FMT_THROW(format_error("unmatched '}' in format string"));
 | 
			
		||||
    return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
 | 
			
		||||
  } else {
 | 
			
		||||
    constexpr auto end = parse_text(str, POS + 1);
 | 
			
		||||
    if constexpr (end - POS > 1) {
 | 
			
		||||
      return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);
 | 
			
		||||
    } else {
 | 
			
		||||
      return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... Args, typename S,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
 | 
			
		||||
constexpr auto compile(S fmt) {
 | 
			
		||||
  constexpr auto str = basic_string_view<typename S::char_type>(fmt);
 | 
			
		||||
  if constexpr (str.size() == 0) {
 | 
			
		||||
    return detail::make_text(str, 0, 0);
 | 
			
		||||
  } else {
 | 
			
		||||
    constexpr auto result =
 | 
			
		||||
        detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif  // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_EXPORT
 | 
			
		||||
 | 
			
		||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
 | 
			
		||||
 | 
			
		||||
template <typename CompiledFormat, typename... Args,
 | 
			
		||||
          typename Char = typename CompiledFormat::char_type,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
 | 
			
		||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
 | 
			
		||||
                                          const Args&... args) {
 | 
			
		||||
  auto s = std::basic_string<Char>();
 | 
			
		||||
  cf.format(std::back_inserter(s), args...);
 | 
			
		||||
  return s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
 | 
			
		||||
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
 | 
			
		||||
                                        const Args&... args) {
 | 
			
		||||
  return cf.format(out, args...);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename S, typename... Args,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
 | 
			
		||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
 | 
			
		||||
                                                           Args&&... args) {
 | 
			
		||||
  if constexpr (std::is_same<typename S::char_type, char>::value) {
 | 
			
		||||
    constexpr auto str = basic_string_view<typename S::char_type>(S());
 | 
			
		||||
    if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
 | 
			
		||||
      const auto& first = detail::first(args...);
 | 
			
		||||
      if constexpr (detail::is_named_arg<
 | 
			
		||||
                        remove_cvref_t<decltype(first)>>::value) {
 | 
			
		||||
        return fmt::to_string(first.value);
 | 
			
		||||
      } else {
 | 
			
		||||
        return fmt::to_string(first);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  constexpr auto compiled = detail::compile<Args...>(S());
 | 
			
		||||
  if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
 | 
			
		||||
                             detail::unknown_format>()) {
 | 
			
		||||
    return fmt::format(
 | 
			
		||||
        static_cast<basic_string_view<typename S::char_type>>(S()),
 | 
			
		||||
        std::forward<Args>(args)...);
 | 
			
		||||
  } else {
 | 
			
		||||
    return fmt::format(compiled, std::forward<Args>(args)...);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename S, typename... Args,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
 | 
			
		||||
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
 | 
			
		||||
  constexpr auto compiled = detail::compile<Args...>(S());
 | 
			
		||||
  if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
 | 
			
		||||
                             detail::unknown_format>()) {
 | 
			
		||||
    return fmt::format_to(
 | 
			
		||||
        out, static_cast<basic_string_view<typename S::char_type>>(S()),
 | 
			
		||||
        std::forward<Args>(args)...);
 | 
			
		||||
  } else {
 | 
			
		||||
    return fmt::format_to(out, compiled, std::forward<Args>(args)...);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename S, typename... Args,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
 | 
			
		||||
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
 | 
			
		||||
    -> format_to_n_result<OutputIt> {
 | 
			
		||||
  using traits = detail::fixed_buffer_traits;
 | 
			
		||||
  auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
 | 
			
		||||
  fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...);
 | 
			
		||||
  return {buf.out(), buf.count()};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename S, typename... Args,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
 | 
			
		||||
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
 | 
			
		||||
    -> size_t {
 | 
			
		||||
  return fmt::format_to(detail::counting_iterator(), fmt, args...).count();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename S, typename... Args,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
 | 
			
		||||
void print(std::FILE* f, const S& fmt, const Args&... args) {
 | 
			
		||||
  memory_buffer buffer;
 | 
			
		||||
  fmt::format_to(std::back_inserter(buffer), fmt, args...);
 | 
			
		||||
  detail::print(f, {buffer.data(), buffer.size()});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename S, typename... Args,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
 | 
			
		||||
void print(const S& fmt, const Args&... args) {
 | 
			
		||||
  print(stdout, fmt, args...);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
 | 
			
		||||
inline namespace literals {
 | 
			
		||||
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
 | 
			
		||||
  using char_t = remove_cvref_t<decltype(Str.data[0])>;
 | 
			
		||||
  return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
 | 
			
		||||
                                     Str>();
 | 
			
		||||
}
 | 
			
		||||
}  // namespace literals
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
FMT_END_EXPORT
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#endif  // FMT_COMPILE_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
// This file is only provided for compatibility and may be removed in future
 | 
			
		||||
// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h
 | 
			
		||||
// otherwise.
 | 
			
		||||
 | 
			
		||||
#include "format.h"
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,439 @@
 | 
			
		|||
// Formatting library for C++ - optional OS-specific functionality
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - present, Victor Zverovich
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_OS_H_
 | 
			
		||||
#define FMT_OS_H_
 | 
			
		||||
 | 
			
		||||
#include "format.h"
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <cerrno>
 | 
			
		||||
#  include <cstddef>
 | 
			
		||||
#  include <cstdio>
 | 
			
		||||
#  include <system_error>  // std::system_error
 | 
			
		||||
 | 
			
		||||
#  if FMT_HAS_INCLUDE(<xlocale.h>)
 | 
			
		||||
#    include <xlocale.h>  // LC_NUMERIC_MASK on macOS
 | 
			
		||||
#  endif
 | 
			
		||||
#endif  // FMT_MODULE
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_USE_FCNTL
 | 
			
		||||
// UWP doesn't provide _pipe.
 | 
			
		||||
#  if FMT_HAS_INCLUDE("winapifamily.h")
 | 
			
		||||
#    include <winapifamily.h>
 | 
			
		||||
#  endif
 | 
			
		||||
#  if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
 | 
			
		||||
       defined(__linux__)) &&                              \
 | 
			
		||||
      (!defined(WINAPI_FAMILY) ||                          \
 | 
			
		||||
       (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
 | 
			
		||||
#    include <fcntl.h>  // for O_RDONLY
 | 
			
		||||
#    define FMT_USE_FCNTL 1
 | 
			
		||||
#  else
 | 
			
		||||
#    define FMT_USE_FCNTL 0
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_POSIX
 | 
			
		||||
#  if defined(_WIN32) && !defined(__MINGW32__)
 | 
			
		||||
// Fix warnings about deprecated symbols.
 | 
			
		||||
#    define FMT_POSIX(call) _##call
 | 
			
		||||
#  else
 | 
			
		||||
#    define FMT_POSIX(call) call
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
 | 
			
		||||
#ifdef FMT_SYSTEM
 | 
			
		||||
#  define FMT_HAS_SYSTEM
 | 
			
		||||
#  define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
 | 
			
		||||
#else
 | 
			
		||||
#  define FMT_SYSTEM(call) ::call
 | 
			
		||||
#  ifdef _WIN32
 | 
			
		||||
// Fix warnings about deprecated symbols.
 | 
			
		||||
#    define FMT_POSIX_CALL(call) ::_##call
 | 
			
		||||
#  else
 | 
			
		||||
#    define FMT_POSIX_CALL(call) ::call
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Retries the expression while it evaluates to error_result and errno
 | 
			
		||||
// equals to EINTR.
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
#  define FMT_RETRY_VAL(result, expression, error_result) \
 | 
			
		||||
    do {                                                  \
 | 
			
		||||
      (result) = (expression);                            \
 | 
			
		||||
    } while ((result) == (error_result) && errno == EINTR)
 | 
			
		||||
#else
 | 
			
		||||
#  define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
FMT_BEGIN_EXPORT
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A reference to a null-terminated string. It can be constructed from a C
 | 
			
		||||
 * string or `std::string`.
 | 
			
		||||
 *
 | 
			
		||||
 * You can use one of the following type aliases for common character types:
 | 
			
		||||
 *
 | 
			
		||||
 * +---------------+-----------------------------+
 | 
			
		||||
 * | Type          | Definition                  |
 | 
			
		||||
 * +===============+=============================+
 | 
			
		||||
 * | cstring_view  | basic_cstring_view<char>    |
 | 
			
		||||
 * +---------------+-----------------------------+
 | 
			
		||||
 * | wcstring_view | basic_cstring_view<wchar_t> |
 | 
			
		||||
 * +---------------+-----------------------------+
 | 
			
		||||
 *
 | 
			
		||||
 * This class is most useful as a parameter type for functions that wrap C APIs.
 | 
			
		||||
 */
 | 
			
		||||
template <typename Char> class basic_cstring_view {
 | 
			
		||||
 private:
 | 
			
		||||
  const Char* data_;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  /// Constructs a string reference object from a C string.
 | 
			
		||||
  basic_cstring_view(const Char* s) : data_(s) {}
 | 
			
		||||
 | 
			
		||||
  /// Constructs a string reference from an `std::string` object.
 | 
			
		||||
  basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
 | 
			
		||||
 | 
			
		||||
  /// Returns the pointer to a C string.
 | 
			
		||||
  auto c_str() const -> const Char* { return data_; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using cstring_view = basic_cstring_view<char>;
 | 
			
		||||
using wcstring_view = basic_cstring_view<wchar_t>;
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
FMT_API const std::error_category& system_category() noexcept;
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
 | 
			
		||||
                                  const char* message) noexcept;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
 | 
			
		||||
                                         format_args args);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constructs a `std::system_error` object with the description of the form
 | 
			
		||||
 *
 | 
			
		||||
 *     <message>: <system-message>
 | 
			
		||||
 *
 | 
			
		||||
 * where `<message>` is the formatted message and `<system-message>` is the
 | 
			
		||||
 * system message corresponding to the error code.
 | 
			
		||||
 * `error_code` is a Windows error code as given by `GetLastError`.
 | 
			
		||||
 * If `error_code` is not a valid error code such as -1, the system message
 | 
			
		||||
 * will look like "error -1".
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     // This throws a system_error with the description
 | 
			
		||||
 *     //   cannot open file 'madeup': The system cannot find the file
 | 
			
		||||
 * specified.
 | 
			
		||||
 *     // or similar (system message may vary).
 | 
			
		||||
 *     const char *filename = "madeup";
 | 
			
		||||
 *     LPOFSTRUCT of = LPOFSTRUCT();
 | 
			
		||||
 *     HFILE file = OpenFile(filename, &of, OF_READ);
 | 
			
		||||
 *     if (file == HFILE_ERROR) {
 | 
			
		||||
 *       throw fmt::windows_error(GetLastError(),
 | 
			
		||||
 *                                "cannot open file '{}'", filename);
 | 
			
		||||
 *     }
 | 
			
		||||
 */
 | 
			
		||||
template <typename... Args>
 | 
			
		||||
std::system_error windows_error(int error_code, string_view message,
 | 
			
		||||
                                const Args&... args) {
 | 
			
		||||
  return vwindows_error(error_code, message, fmt::make_format_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reports a Windows error without throwing an exception.
 | 
			
		||||
// Can be used to report errors from destructors.
 | 
			
		||||
FMT_API void report_windows_error(int error_code, const char* message) noexcept;
 | 
			
		||||
#else
 | 
			
		||||
inline auto system_category() noexcept -> const std::error_category& {
 | 
			
		||||
  return std::system_category();
 | 
			
		||||
}
 | 
			
		||||
#endif  // _WIN32
 | 
			
		||||
 | 
			
		||||
// std::system is not available on some platforms such as iOS (#2248).
 | 
			
		||||
#ifdef __OSX__
 | 
			
		||||
template <typename S, typename... Args, typename Char = char_t<S>>
 | 
			
		||||
void say(const S& format_str, Args&&... args) {
 | 
			
		||||
  std::system(format("say \"{}\"", format(format_str, args...)).c_str());
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// A buffered file.
 | 
			
		||||
class buffered_file {
 | 
			
		||||
 private:
 | 
			
		||||
  FILE* file_;
 | 
			
		||||
 | 
			
		||||
  friend class file;
 | 
			
		||||
 | 
			
		||||
  explicit buffered_file(FILE* f) : file_(f) {}
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  buffered_file(const buffered_file&) = delete;
 | 
			
		||||
  void operator=(const buffered_file&) = delete;
 | 
			
		||||
 | 
			
		||||
  // Constructs a buffered_file object which doesn't represent any file.
 | 
			
		||||
  buffered_file() noexcept : file_(nullptr) {}
 | 
			
		||||
 | 
			
		||||
  // Destroys the object closing the file it represents if any.
 | 
			
		||||
  FMT_API ~buffered_file() noexcept;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
 | 
			
		||||
    other.file_ = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto operator=(buffered_file&& other) -> buffered_file& {
 | 
			
		||||
    close();
 | 
			
		||||
    file_ = other.file_;
 | 
			
		||||
    other.file_ = nullptr;
 | 
			
		||||
    return *this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Opens a file.
 | 
			
		||||
  FMT_API buffered_file(cstring_view filename, cstring_view mode);
 | 
			
		||||
 | 
			
		||||
  // Closes the file.
 | 
			
		||||
  FMT_API void close();
 | 
			
		||||
 | 
			
		||||
  // Returns the pointer to a FILE object representing this file.
 | 
			
		||||
  auto get() const noexcept -> FILE* { return file_; }
 | 
			
		||||
 | 
			
		||||
  FMT_API auto descriptor() const -> int;
 | 
			
		||||
 | 
			
		||||
  template <typename... T>
 | 
			
		||||
  inline void print(string_view fmt, const T&... args) {
 | 
			
		||||
    const auto& vargs = fmt::make_format_args(args...);
 | 
			
		||||
    detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
 | 
			
		||||
                               : fmt::vprint(file_, fmt, vargs);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if FMT_USE_FCNTL
 | 
			
		||||
 | 
			
		||||
// A file. Closed file is represented by a file object with descriptor -1.
 | 
			
		||||
// Methods that are not declared with noexcept may throw
 | 
			
		||||
// fmt::system_error in case of failure. Note that some errors such as
 | 
			
		||||
// closing the file multiple times will cause a crash on Windows rather
 | 
			
		||||
// than an exception. You can get standard behavior by overriding the
 | 
			
		||||
// invalid parameter handler with _set_invalid_parameter_handler.
 | 
			
		||||
class FMT_API file {
 | 
			
		||||
 private:
 | 
			
		||||
  int fd_;  // File descriptor.
 | 
			
		||||
 | 
			
		||||
  // Constructs a file object with a given descriptor.
 | 
			
		||||
  explicit file(int fd) : fd_(fd) {}
 | 
			
		||||
 | 
			
		||||
  friend struct pipe;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  // Possible values for the oflag argument to the constructor.
 | 
			
		||||
  enum {
 | 
			
		||||
    RDONLY = FMT_POSIX(O_RDONLY),  // Open for reading only.
 | 
			
		||||
    WRONLY = FMT_POSIX(O_WRONLY),  // Open for writing only.
 | 
			
		||||
    RDWR = FMT_POSIX(O_RDWR),      // Open for reading and writing.
 | 
			
		||||
    CREATE = FMT_POSIX(O_CREAT),   // Create if the file doesn't exist.
 | 
			
		||||
    APPEND = FMT_POSIX(O_APPEND),  // Open in append mode.
 | 
			
		||||
    TRUNC = FMT_POSIX(O_TRUNC)     // Truncate the content of the file.
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Constructs a file object which doesn't represent any file.
 | 
			
		||||
  file() noexcept : fd_(-1) {}
 | 
			
		||||
 | 
			
		||||
  // Opens a file and constructs a file object representing this file.
 | 
			
		||||
  file(cstring_view path, int oflag);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  file(const file&) = delete;
 | 
			
		||||
  void operator=(const file&) = delete;
 | 
			
		||||
 | 
			
		||||
  file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
 | 
			
		||||
 | 
			
		||||
  // Move assignment is not noexcept because close may throw.
 | 
			
		||||
  auto operator=(file&& other) -> file& {
 | 
			
		||||
    close();
 | 
			
		||||
    fd_ = other.fd_;
 | 
			
		||||
    other.fd_ = -1;
 | 
			
		||||
    return *this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Destroys the object closing the file it represents if any.
 | 
			
		||||
  ~file() noexcept;
 | 
			
		||||
 | 
			
		||||
  // Returns the file descriptor.
 | 
			
		||||
  auto descriptor() const noexcept -> int { return fd_; }
 | 
			
		||||
 | 
			
		||||
  // Closes the file.
 | 
			
		||||
  void close();
 | 
			
		||||
 | 
			
		||||
  // Returns the file size. The size has signed type for consistency with
 | 
			
		||||
  // stat::st_size.
 | 
			
		||||
  auto size() const -> long long;
 | 
			
		||||
 | 
			
		||||
  // Attempts to read count bytes from the file into the specified buffer.
 | 
			
		||||
  auto read(void* buffer, size_t count) -> size_t;
 | 
			
		||||
 | 
			
		||||
  // Attempts to write count bytes from the specified buffer to the file.
 | 
			
		||||
  auto write(const void* buffer, size_t count) -> size_t;
 | 
			
		||||
 | 
			
		||||
  // Duplicates a file descriptor with the dup function and returns
 | 
			
		||||
  // the duplicate as a file object.
 | 
			
		||||
  static auto dup(int fd) -> file;
 | 
			
		||||
 | 
			
		||||
  // Makes fd be the copy of this file descriptor, closing fd first if
 | 
			
		||||
  // necessary.
 | 
			
		||||
  void dup2(int fd);
 | 
			
		||||
 | 
			
		||||
  // Makes fd be the copy of this file descriptor, closing fd first if
 | 
			
		||||
  // necessary.
 | 
			
		||||
  void dup2(int fd, std::error_code& ec) noexcept;
 | 
			
		||||
 | 
			
		||||
  // Creates a buffered_file object associated with this file and detaches
 | 
			
		||||
  // this file object from the file.
 | 
			
		||||
  auto fdopen(const char* mode) -> buffered_file;
 | 
			
		||||
 | 
			
		||||
#  if defined(_WIN32) && !defined(__MINGW32__)
 | 
			
		||||
  // Opens a file and constructs a file object representing this file by
 | 
			
		||||
  // wcstring_view filename. Windows only.
 | 
			
		||||
  static file open_windows_file(wcstring_view path, int oflag);
 | 
			
		||||
#  endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct FMT_API pipe {
 | 
			
		||||
  file read_end;
 | 
			
		||||
  file write_end;
 | 
			
		||||
 | 
			
		||||
  // Creates a pipe setting up read_end and write_end file objects for reading
 | 
			
		||||
  // and writing respectively.
 | 
			
		||||
  pipe();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Returns the memory page size.
 | 
			
		||||
auto getpagesize() -> long;
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
struct buffer_size {
 | 
			
		||||
  buffer_size() = default;
 | 
			
		||||
  size_t value = 0;
 | 
			
		||||
  auto operator=(size_t val) const -> buffer_size {
 | 
			
		||||
    auto bs = buffer_size();
 | 
			
		||||
    bs.value = val;
 | 
			
		||||
    return bs;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ostream_params {
 | 
			
		||||
  int oflag = file::WRONLY | file::CREATE | file::TRUNC;
 | 
			
		||||
  size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
 | 
			
		||||
 | 
			
		||||
  ostream_params() {}
 | 
			
		||||
 | 
			
		||||
  template <typename... T>
 | 
			
		||||
  ostream_params(T... params, int new_oflag) : ostream_params(params...) {
 | 
			
		||||
    oflag = new_oflag;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename... T>
 | 
			
		||||
  ostream_params(T... params, detail::buffer_size bs)
 | 
			
		||||
      : ostream_params(params...) {
 | 
			
		||||
    this->buffer_size = bs.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
// Intel has a bug that results in failure to deduce a constructor
 | 
			
		||||
// for empty parameter packs.
 | 
			
		||||
#  if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
 | 
			
		||||
  ostream_params(int new_oflag) : oflag(new_oflag) {}
 | 
			
		||||
  ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
 | 
			
		||||
#  endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class file_buffer final : public buffer<char> {
 | 
			
		||||
 private:
 | 
			
		||||
  file file_;
 | 
			
		||||
 | 
			
		||||
  FMT_API static void grow(buffer<char>& buf, size_t);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_API file_buffer(cstring_view path, const ostream_params& params);
 | 
			
		||||
  FMT_API file_buffer(file_buffer&& other) noexcept;
 | 
			
		||||
  FMT_API ~file_buffer();
 | 
			
		||||
 | 
			
		||||
  void flush() {
 | 
			
		||||
    if (size() == 0) return;
 | 
			
		||||
    file_.write(data(), size() * sizeof(data()[0]));
 | 
			
		||||
    clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void close() {
 | 
			
		||||
    flush();
 | 
			
		||||
    file_.close();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
constexpr auto buffer_size = detail::buffer_size();
 | 
			
		||||
 | 
			
		||||
/// A fast output stream for writing from a single thread. Writing from
 | 
			
		||||
/// multiple threads without external synchronization may result in a data race.
 | 
			
		||||
class FMT_API ostream {
 | 
			
		||||
 private:
 | 
			
		||||
  FMT_MSC_WARNING(suppress : 4251)
 | 
			
		||||
  detail::file_buffer buffer_;
 | 
			
		||||
 | 
			
		||||
  ostream(cstring_view path, const detail::ostream_params& params)
 | 
			
		||||
      : buffer_(path, params) {}
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
 | 
			
		||||
 | 
			
		||||
  ~ostream();
 | 
			
		||||
 | 
			
		||||
  void flush() { buffer_.flush(); }
 | 
			
		||||
 | 
			
		||||
  template <typename... T>
 | 
			
		||||
  friend auto output_file(cstring_view path, T... params) -> ostream;
 | 
			
		||||
 | 
			
		||||
  void close() { buffer_.close(); }
 | 
			
		||||
 | 
			
		||||
  /// Formats `args` according to specifications in `fmt` and writes the
 | 
			
		||||
  /// output to the file.
 | 
			
		||||
  template <typename... T> void print(format_string<T...> fmt, T&&... args) {
 | 
			
		||||
    vformat_to(appender(buffer_), fmt, fmt::make_format_args(args...));
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Opens a file for writing. Supported parameters passed in `params`:
 | 
			
		||||
 *
 | 
			
		||||
 * - `<integer>`: Flags passed to [open](
 | 
			
		||||
 *   https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html)
 | 
			
		||||
 *   (`file::WRONLY | file::CREATE | file::TRUNC` by default)
 | 
			
		||||
 * - `buffer_size=<integer>`: Output buffer size
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     auto out = fmt::output_file("guide.txt");
 | 
			
		||||
 *     out.print("Don't {}", "Panic");
 | 
			
		||||
 */
 | 
			
		||||
template <typename... T>
 | 
			
		||||
inline auto output_file(cstring_view path, T... params) -> ostream {
 | 
			
		||||
  return {path, detail::ostream_params(params...)};
 | 
			
		||||
}
 | 
			
		||||
#endif  // FMT_USE_FCNTL
 | 
			
		||||
 | 
			
		||||
FMT_END_EXPORT
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#endif  // FMT_OS_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,211 @@
 | 
			
		|||
// Formatting library for C++ - std::ostream support
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - present, Victor Zverovich
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_OSTREAM_H_
 | 
			
		||||
#define FMT_OSTREAM_H_
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <fstream>  // std::filebuf
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
#  ifdef __GLIBCXX__
 | 
			
		||||
#    include <ext/stdio_filebuf.h>
 | 
			
		||||
#    include <ext/stdio_sync_filebuf.h>
 | 
			
		||||
#  endif
 | 
			
		||||
#  include <io.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "chrono.h"  // formatbuf
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
// Generate a unique explicit instantion in every translation unit using a tag
 | 
			
		||||
// type in an anonymous namespace.
 | 
			
		||||
namespace {
 | 
			
		||||
struct file_access_tag {};
 | 
			
		||||
}  // namespace
 | 
			
		||||
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
 | 
			
		||||
class file_access {
 | 
			
		||||
  friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if FMT_MSC_VERSION
 | 
			
		||||
template class file_access<file_access_tag, std::filebuf,
 | 
			
		||||
                           &std::filebuf::_Myfile>;
 | 
			
		||||
auto get_file(std::filebuf&) -> FILE*;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
 | 
			
		||||
    -> bool {
 | 
			
		||||
  FILE* f = nullptr;
 | 
			
		||||
#if FMT_MSC_VERSION && FMT_USE_RTTI
 | 
			
		||||
  if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
 | 
			
		||||
    f = get_file(*buf);
 | 
			
		||||
  else
 | 
			
		||||
    return false;
 | 
			
		||||
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
 | 
			
		||||
  auto* rdbuf = os.rdbuf();
 | 
			
		||||
  if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
 | 
			
		||||
    f = sfbuf->file();
 | 
			
		||||
  else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
 | 
			
		||||
    f = fbuf->file();
 | 
			
		||||
  else
 | 
			
		||||
    return false;
 | 
			
		||||
#else
 | 
			
		||||
  ignore_unused(os, data, f);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
  if (f) {
 | 
			
		||||
    int fd = _fileno(f);
 | 
			
		||||
    if (_isatty(fd)) {
 | 
			
		||||
      os.flush();
 | 
			
		||||
      return write_console(fd, data);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
inline auto write_ostream_unicode(std::wostream&,
 | 
			
		||||
                                  fmt::basic_string_view<wchar_t>) -> bool {
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write the content of buf to os.
 | 
			
		||||
// It is a separate function rather than a part of vprint to simplify testing.
 | 
			
		||||
template <typename Char>
 | 
			
		||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
 | 
			
		||||
  const Char* buf_data = buf.data();
 | 
			
		||||
  using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
 | 
			
		||||
  unsigned_streamsize size = buf.size();
 | 
			
		||||
  unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
 | 
			
		||||
  do {
 | 
			
		||||
    unsigned_streamsize n = size <= max_size ? size : max_size;
 | 
			
		||||
    os.write(buf_data, static_cast<std::streamsize>(n));
 | 
			
		||||
    buf_data += n;
 | 
			
		||||
    size -= n;
 | 
			
		||||
  } while (size != 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename T>
 | 
			
		||||
void format_value(buffer<Char>& buf, const T& value) {
 | 
			
		||||
  auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
 | 
			
		||||
  auto&& output = std::basic_ostream<Char>(&format_buf);
 | 
			
		||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
 | 
			
		||||
  output.imbue(std::locale::classic());  // The default is always unlocalized.
 | 
			
		||||
#endif
 | 
			
		||||
  output << value;
 | 
			
		||||
  output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T> struct streamed_view {
 | 
			
		||||
  const T& value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
// Formats an object of type T that has an overloaded ostream operator<<.
 | 
			
		||||
template <typename Char>
 | 
			
		||||
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
 | 
			
		||||
  void set_debug_format() = delete;
 | 
			
		||||
 | 
			
		||||
  template <typename T, typename Context>
 | 
			
		||||
  auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
 | 
			
		||||
    auto buffer = basic_memory_buffer<Char>();
 | 
			
		||||
    detail::format_value(buffer, value);
 | 
			
		||||
    return formatter<basic_string_view<Char>, Char>::format(
 | 
			
		||||
        {buffer.data(), buffer.size()}, ctx);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using ostream_formatter = basic_ostream_formatter<char>;
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Char>
 | 
			
		||||
struct formatter<detail::streamed_view<T>, Char>
 | 
			
		||||
    : basic_ostream_formatter<Char> {
 | 
			
		||||
  template <typename Context>
 | 
			
		||||
  auto format(detail::streamed_view<T> view, Context& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    return basic_ostream_formatter<Char>::format(view.value, ctx);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a view that formats `value` via an ostream `operator<<`.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     fmt::print("Current thread id: {}\n",
 | 
			
		||||
 *                fmt::streamed(std::this_thread::get_id()));
 | 
			
		||||
 */
 | 
			
		||||
template <typename T>
 | 
			
		||||
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
 | 
			
		||||
  return {value};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
inline void vprint_directly(std::ostream& os, string_view format_str,
 | 
			
		||||
                            format_args args) {
 | 
			
		||||
  auto buffer = memory_buffer();
 | 
			
		||||
  detail::vformat_to(buffer, format_str, args);
 | 
			
		||||
  detail::write_buffer(os, buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT template <typename Char>
 | 
			
		||||
void vprint(std::basic_ostream<Char>& os,
 | 
			
		||||
            basic_string_view<type_identity_t<Char>> format_str,
 | 
			
		||||
            typename detail::vformat_args<Char>::type args) {
 | 
			
		||||
  auto buffer = basic_memory_buffer<Char>();
 | 
			
		||||
  detail::vformat_to(buffer, format_str, args);
 | 
			
		||||
  if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
 | 
			
		||||
  detail::write_buffer(os, buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Prints formatted data to the stream `os`.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     fmt::print(cerr, "Don't {}!", "panic");
 | 
			
		||||
 */
 | 
			
		||||
FMT_EXPORT template <typename... T>
 | 
			
		||||
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
 | 
			
		||||
  const auto& vargs = fmt::make_format_args(args...);
 | 
			
		||||
  if (detail::use_utf8())
 | 
			
		||||
    vprint(os, fmt, vargs);
 | 
			
		||||
  else
 | 
			
		||||
    detail::vprint_directly(os, fmt, vargs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename... Args>
 | 
			
		||||
void print(std::wostream& os,
 | 
			
		||||
           basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
 | 
			
		||||
           Args&&... args) {
 | 
			
		||||
  vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT template <typename... T>
 | 
			
		||||
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
 | 
			
		||||
  fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename... Args>
 | 
			
		||||
void println(std::wostream& os,
 | 
			
		||||
             basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
 | 
			
		||||
             Args&&... args) {
 | 
			
		||||
  print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#endif  // FMT_OSTREAM_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,656 @@
 | 
			
		|||
// Formatting library for C++ - legacy printf implementation
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - 2016, Victor Zverovich
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_PRINTF_H_
 | 
			
		||||
#define FMT_PRINTF_H_
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <algorithm>  // std::max
 | 
			
		||||
#  include <limits>     // std::numeric_limits
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "format.h"
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
FMT_BEGIN_EXPORT
 | 
			
		||||
 | 
			
		||||
template <typename T> struct printf_formatter {
 | 
			
		||||
  printf_formatter() = delete;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char> class basic_printf_context {
 | 
			
		||||
 private:
 | 
			
		||||
  basic_appender<Char> out_;
 | 
			
		||||
  basic_format_args<basic_printf_context> args_;
 | 
			
		||||
 | 
			
		||||
  static_assert(std::is_same<Char, char>::value ||
 | 
			
		||||
                    std::is_same<Char, wchar_t>::value,
 | 
			
		||||
                "Unsupported code unit type.");
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  using char_type = Char;
 | 
			
		||||
  using parse_context_type = basic_format_parse_context<Char>;
 | 
			
		||||
  template <typename T> using formatter_type = printf_formatter<T>;
 | 
			
		||||
 | 
			
		||||
  /// Constructs a `printf_context` object. References to the arguments are
 | 
			
		||||
  /// stored in the context object so make sure they have appropriate lifetimes.
 | 
			
		||||
  basic_printf_context(basic_appender<Char> out,
 | 
			
		||||
                       basic_format_args<basic_printf_context> args)
 | 
			
		||||
      : out_(out), args_(args) {}
 | 
			
		||||
 | 
			
		||||
  auto out() -> basic_appender<Char> { return out_; }
 | 
			
		||||
  void advance_to(basic_appender<Char>) {}
 | 
			
		||||
 | 
			
		||||
  auto locale() -> detail::locale_ref { return {}; }
 | 
			
		||||
 | 
			
		||||
  auto arg(int id) const -> basic_format_arg<basic_printf_context> {
 | 
			
		||||
    return args_.get(id);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
// Checks if a value fits in int - used to avoid warnings about comparing
 | 
			
		||||
// signed and unsigned integers.
 | 
			
		||||
template <bool IsSigned> struct int_checker {
 | 
			
		||||
  template <typename T> static auto fits_in_int(T value) -> bool {
 | 
			
		||||
    unsigned max = to_unsigned(max_value<int>());
 | 
			
		||||
    return value <= max;
 | 
			
		||||
  }
 | 
			
		||||
  static auto fits_in_int(bool) -> bool { return true; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <> struct int_checker<true> {
 | 
			
		||||
  template <typename T> static auto fits_in_int(T value) -> bool {
 | 
			
		||||
    return value >= (std::numeric_limits<int>::min)() &&
 | 
			
		||||
           value <= max_value<int>();
 | 
			
		||||
  }
 | 
			
		||||
  static auto fits_in_int(int) -> bool { return true; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct printf_precision_handler {
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
 | 
			
		||||
  auto operator()(T value) -> int {
 | 
			
		||||
    if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
 | 
			
		||||
      report_error("number is too big");
 | 
			
		||||
    return (std::max)(static_cast<int>(value), 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
 | 
			
		||||
  auto operator()(T) -> int {
 | 
			
		||||
    report_error("precision is not integer");
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// An argument visitor that returns true iff arg is a zero integer.
 | 
			
		||||
struct is_zero_int {
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
 | 
			
		||||
  auto operator()(T value) -> bool {
 | 
			
		||||
    return value == 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
 | 
			
		||||
  auto operator()(T) -> bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
 | 
			
		||||
 | 
			
		||||
template <> struct make_unsigned_or_bool<bool> {
 | 
			
		||||
  using type = bool;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Context> class arg_converter {
 | 
			
		||||
 private:
 | 
			
		||||
  using char_type = typename Context::char_type;
 | 
			
		||||
 | 
			
		||||
  basic_format_arg<Context>& arg_;
 | 
			
		||||
  char_type type_;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  arg_converter(basic_format_arg<Context>& arg, char_type type)
 | 
			
		||||
      : arg_(arg), type_(type) {}
 | 
			
		||||
 | 
			
		||||
  void operator()(bool value) {
 | 
			
		||||
    if (type_ != 's') operator()<bool>(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
 | 
			
		||||
  void operator()(U value) {
 | 
			
		||||
    bool is_signed = type_ == 'd' || type_ == 'i';
 | 
			
		||||
    using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
 | 
			
		||||
    if (const_check(sizeof(target_type) <= sizeof(int))) {
 | 
			
		||||
      // Extra casts are used to silence warnings.
 | 
			
		||||
      if (is_signed) {
 | 
			
		||||
        auto n = static_cast<int>(static_cast<target_type>(value));
 | 
			
		||||
        arg_ = detail::make_arg<Context>(n);
 | 
			
		||||
      } else {
 | 
			
		||||
        using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
 | 
			
		||||
        auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
 | 
			
		||||
        arg_ = detail::make_arg<Context>(n);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      if (is_signed) {
 | 
			
		||||
        // glibc's printf doesn't sign extend arguments of smaller types:
 | 
			
		||||
        //   std::printf("%lld", -42);  // prints "4294967254"
 | 
			
		||||
        // but we don't have to do the same because it's a UB.
 | 
			
		||||
        auto n = static_cast<long long>(value);
 | 
			
		||||
        arg_ = detail::make_arg<Context>(n);
 | 
			
		||||
      } else {
 | 
			
		||||
        auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
 | 
			
		||||
        arg_ = detail::make_arg<Context>(n);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
 | 
			
		||||
  void operator()(U) {}  // No conversion needed for non-integral types.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Converts an integer argument to T for printf, if T is an integral type.
 | 
			
		||||
// If T is void, the argument is converted to corresponding signed or unsigned
 | 
			
		||||
// type depending on the type specifier: 'd' and 'i' - signed, other -
 | 
			
		||||
// unsigned).
 | 
			
		||||
template <typename T, typename Context, typename Char>
 | 
			
		||||
void convert_arg(basic_format_arg<Context>& arg, Char type) {
 | 
			
		||||
  arg.visit(arg_converter<T, Context>(arg, type));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Converts an integer argument to char for printf.
 | 
			
		||||
template <typename Context> class char_converter {
 | 
			
		||||
 private:
 | 
			
		||||
  basic_format_arg<Context>& arg_;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
 | 
			
		||||
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
 | 
			
		||||
  void operator()(T value) {
 | 
			
		||||
    auto c = static_cast<typename Context::char_type>(value);
 | 
			
		||||
    arg_ = detail::make_arg<Context>(c);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
 | 
			
		||||
  void operator()(T) {}  // No conversion needed for non-integral types.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// An argument visitor that return a pointer to a C string if argument is a
 | 
			
		||||
// string or null otherwise.
 | 
			
		||||
template <typename Char> struct get_cstring {
 | 
			
		||||
  template <typename T> auto operator()(T) -> const Char* { return nullptr; }
 | 
			
		||||
  auto operator()(const Char* s) -> const Char* { return s; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Checks if an argument is a valid printf width specifier and sets
 | 
			
		||||
// left alignment if it is negative.
 | 
			
		||||
class printf_width_handler {
 | 
			
		||||
 private:
 | 
			
		||||
  format_specs& specs_;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
 | 
			
		||||
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
 | 
			
		||||
  auto operator()(T value) -> unsigned {
 | 
			
		||||
    auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
 | 
			
		||||
    if (detail::is_negative(value)) {
 | 
			
		||||
      specs_.align = align::left;
 | 
			
		||||
      width = 0 - width;
 | 
			
		||||
    }
 | 
			
		||||
    unsigned int_max = to_unsigned(max_value<int>());
 | 
			
		||||
    if (width > int_max) report_error("number is too big");
 | 
			
		||||
    return static_cast<unsigned>(width);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
 | 
			
		||||
  auto operator()(T) -> unsigned {
 | 
			
		||||
    report_error("width is not integer");
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Workaround for a bug with the XL compiler when initializing
 | 
			
		||||
// printf_arg_formatter's base class.
 | 
			
		||||
template <typename Char>
 | 
			
		||||
auto make_arg_formatter(basic_appender<Char> iter, format_specs& s)
 | 
			
		||||
    -> arg_formatter<Char> {
 | 
			
		||||
  return {iter, s, locale_ref()};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The `printf` argument formatter.
 | 
			
		||||
template <typename Char>
 | 
			
		||||
class printf_arg_formatter : public arg_formatter<Char> {
 | 
			
		||||
 private:
 | 
			
		||||
  using base = arg_formatter<Char>;
 | 
			
		||||
  using context_type = basic_printf_context<Char>;
 | 
			
		||||
 | 
			
		||||
  context_type& context_;
 | 
			
		||||
 | 
			
		||||
  void write_null_pointer(bool is_string = false) {
 | 
			
		||||
    auto s = this->specs;
 | 
			
		||||
    s.type = presentation_type::none;
 | 
			
		||||
    write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
 | 
			
		||||
                       context_type& ctx)
 | 
			
		||||
      : base(make_arg_formatter(iter, s)), context_(ctx) {}
 | 
			
		||||
 | 
			
		||||
  void operator()(monostate value) { base::operator()(value); }
 | 
			
		||||
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
 | 
			
		||||
  void operator()(T value) {
 | 
			
		||||
    // MSVC2013 fails to compile separate overloads for bool and Char so use
 | 
			
		||||
    // std::is_same instead.
 | 
			
		||||
    if (!std::is_same<T, Char>::value) {
 | 
			
		||||
      base::operator()(value);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    format_specs s = this->specs;
 | 
			
		||||
    if (s.type != presentation_type::none && s.type != presentation_type::chr) {
 | 
			
		||||
      return (*this)(static_cast<int>(value));
 | 
			
		||||
    }
 | 
			
		||||
    s.sign = sign::none;
 | 
			
		||||
    s.alt = false;
 | 
			
		||||
    s.fill = ' ';  // Ignore '0' flag for char types.
 | 
			
		||||
    // align::numeric needs to be overwritten here since the '0' flag is
 | 
			
		||||
    // ignored for non-numeric types
 | 
			
		||||
    if (s.align == align::none || s.align == align::numeric)
 | 
			
		||||
      s.align = align::right;
 | 
			
		||||
    write<Char>(this->out, static_cast<Char>(value), s);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
 | 
			
		||||
  void operator()(T value) {
 | 
			
		||||
    base::operator()(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void operator()(const char* value) {
 | 
			
		||||
    if (value)
 | 
			
		||||
      base::operator()(value);
 | 
			
		||||
    else
 | 
			
		||||
      write_null_pointer(this->specs.type != presentation_type::pointer);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void operator()(const wchar_t* value) {
 | 
			
		||||
    if (value)
 | 
			
		||||
      base::operator()(value);
 | 
			
		||||
    else
 | 
			
		||||
      write_null_pointer(this->specs.type != presentation_type::pointer);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void operator()(basic_string_view<Char> value) { base::operator()(value); }
 | 
			
		||||
 | 
			
		||||
  void operator()(const void* value) {
 | 
			
		||||
    if (value)
 | 
			
		||||
      base::operator()(value);
 | 
			
		||||
    else
 | 
			
		||||
      write_null_pointer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void operator()(typename basic_format_arg<context_type>::handle handle) {
 | 
			
		||||
    auto parse_ctx = basic_format_parse_context<Char>({});
 | 
			
		||||
    handle.format(parse_ctx, context_);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
 | 
			
		||||
  for (; it != end; ++it) {
 | 
			
		||||
    switch (*it) {
 | 
			
		||||
    case '-':
 | 
			
		||||
      specs.align = align::left;
 | 
			
		||||
      break;
 | 
			
		||||
    case '+':
 | 
			
		||||
      specs.sign = sign::plus;
 | 
			
		||||
      break;
 | 
			
		||||
    case '0':
 | 
			
		||||
      specs.fill = '0';
 | 
			
		||||
      break;
 | 
			
		||||
    case ' ':
 | 
			
		||||
      if (specs.sign != sign::plus) specs.sign = sign::space;
 | 
			
		||||
      break;
 | 
			
		||||
    case '#':
 | 
			
		||||
      specs.alt = true;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename GetArg>
 | 
			
		||||
auto parse_header(const Char*& it, const Char* end, format_specs& specs,
 | 
			
		||||
                  GetArg get_arg) -> int {
 | 
			
		||||
  int arg_index = -1;
 | 
			
		||||
  Char c = *it;
 | 
			
		||||
  if (c >= '0' && c <= '9') {
 | 
			
		||||
    // Parse an argument index (if followed by '$') or a width possibly
 | 
			
		||||
    // preceded with '0' flag(s).
 | 
			
		||||
    int value = parse_nonnegative_int(it, end, -1);
 | 
			
		||||
    if (it != end && *it == '$') {  // value is an argument index
 | 
			
		||||
      ++it;
 | 
			
		||||
      arg_index = value != -1 ? value : max_value<int>();
 | 
			
		||||
    } else {
 | 
			
		||||
      if (c == '0') specs.fill = '0';
 | 
			
		||||
      if (value != 0) {
 | 
			
		||||
        // Nonzero value means that we parsed width and don't need to
 | 
			
		||||
        // parse it or flags again, so return now.
 | 
			
		||||
        if (value == -1) report_error("number is too big");
 | 
			
		||||
        specs.width = value;
 | 
			
		||||
        return arg_index;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  parse_flags(specs, it, end);
 | 
			
		||||
  // Parse width.
 | 
			
		||||
  if (it != end) {
 | 
			
		||||
    if (*it >= '0' && *it <= '9') {
 | 
			
		||||
      specs.width = parse_nonnegative_int(it, end, -1);
 | 
			
		||||
      if (specs.width == -1) report_error("number is too big");
 | 
			
		||||
    } else if (*it == '*') {
 | 
			
		||||
      ++it;
 | 
			
		||||
      specs.width = static_cast<int>(
 | 
			
		||||
          get_arg(-1).visit(detail::printf_width_handler(specs)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return arg_index;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline auto parse_printf_presentation_type(char c, type t, bool& upper)
 | 
			
		||||
    -> presentation_type {
 | 
			
		||||
  using pt = presentation_type;
 | 
			
		||||
  constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
 | 
			
		||||
  switch (c) {
 | 
			
		||||
  case 'd':
 | 
			
		||||
    return in(t, integral_set) ? pt::dec : pt::none;
 | 
			
		||||
  case 'o':
 | 
			
		||||
    return in(t, integral_set) ? pt::oct : pt::none;
 | 
			
		||||
  case 'X':
 | 
			
		||||
    upper = true;
 | 
			
		||||
    FMT_FALLTHROUGH;
 | 
			
		||||
  case 'x':
 | 
			
		||||
    return in(t, integral_set) ? pt::hex : pt::none;
 | 
			
		||||
  case 'E':
 | 
			
		||||
    upper = true;
 | 
			
		||||
    FMT_FALLTHROUGH;
 | 
			
		||||
  case 'e':
 | 
			
		||||
    return in(t, float_set) ? pt::exp : pt::none;
 | 
			
		||||
  case 'F':
 | 
			
		||||
    upper = true;
 | 
			
		||||
    FMT_FALLTHROUGH;
 | 
			
		||||
  case 'f':
 | 
			
		||||
    return in(t, float_set) ? pt::fixed : pt::none;
 | 
			
		||||
  case 'G':
 | 
			
		||||
    upper = true;
 | 
			
		||||
    FMT_FALLTHROUGH;
 | 
			
		||||
  case 'g':
 | 
			
		||||
    return in(t, float_set) ? pt::general : pt::none;
 | 
			
		||||
  case 'A':
 | 
			
		||||
    upper = true;
 | 
			
		||||
    FMT_FALLTHROUGH;
 | 
			
		||||
  case 'a':
 | 
			
		||||
    return in(t, float_set) ? pt::hexfloat : pt::none;
 | 
			
		||||
  case 'c':
 | 
			
		||||
    return in(t, integral_set) ? pt::chr : pt::none;
 | 
			
		||||
  case 's':
 | 
			
		||||
    return in(t, string_set | cstring_set) ? pt::string : pt::none;
 | 
			
		||||
  case 'p':
 | 
			
		||||
    return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
 | 
			
		||||
  default:
 | 
			
		||||
    return pt::none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename Context>
 | 
			
		||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
 | 
			
		||||
             basic_format_args<Context> args) {
 | 
			
		||||
  using iterator = basic_appender<Char>;
 | 
			
		||||
  auto out = iterator(buf);
 | 
			
		||||
  auto context = basic_printf_context<Char>(out, args);
 | 
			
		||||
  auto parse_ctx = basic_format_parse_context<Char>(format);
 | 
			
		||||
 | 
			
		||||
  // Returns the argument with specified index or, if arg_index is -1, the next
 | 
			
		||||
  // argument.
 | 
			
		||||
  auto get_arg = [&](int arg_index) {
 | 
			
		||||
    if (arg_index < 0)
 | 
			
		||||
      arg_index = parse_ctx.next_arg_id();
 | 
			
		||||
    else
 | 
			
		||||
      parse_ctx.check_arg_id(--arg_index);
 | 
			
		||||
    return detail::get_arg(context, arg_index);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const Char* start = parse_ctx.begin();
 | 
			
		||||
  const Char* end = parse_ctx.end();
 | 
			
		||||
  auto it = start;
 | 
			
		||||
  while (it != end) {
 | 
			
		||||
    if (!find<false, Char>(it, end, '%', it)) {
 | 
			
		||||
      it = end;  // find leaves it == nullptr if it doesn't find '%'.
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    Char c = *it++;
 | 
			
		||||
    if (it != end && *it == c) {
 | 
			
		||||
      write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
 | 
			
		||||
      start = ++it;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
 | 
			
		||||
 | 
			
		||||
    auto specs = format_specs();
 | 
			
		||||
    specs.align = align::right;
 | 
			
		||||
 | 
			
		||||
    // Parse argument index, flags and width.
 | 
			
		||||
    int arg_index = parse_header(it, end, specs, get_arg);
 | 
			
		||||
    if (arg_index == 0) report_error("argument not found");
 | 
			
		||||
 | 
			
		||||
    // Parse precision.
 | 
			
		||||
    if (it != end && *it == '.') {
 | 
			
		||||
      ++it;
 | 
			
		||||
      c = it != end ? *it : 0;
 | 
			
		||||
      if ('0' <= c && c <= '9') {
 | 
			
		||||
        specs.precision = parse_nonnegative_int(it, end, 0);
 | 
			
		||||
      } else if (c == '*') {
 | 
			
		||||
        ++it;
 | 
			
		||||
        specs.precision =
 | 
			
		||||
            static_cast<int>(get_arg(-1).visit(printf_precision_handler()));
 | 
			
		||||
      } else {
 | 
			
		||||
        specs.precision = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto arg = get_arg(arg_index);
 | 
			
		||||
    // For d, i, o, u, x, and X conversion specifiers, if a precision is
 | 
			
		||||
    // specified, the '0' flag is ignored
 | 
			
		||||
    if (specs.precision >= 0 && arg.is_integral()) {
 | 
			
		||||
      // Ignore '0' for non-numeric types or if '-' present.
 | 
			
		||||
      specs.fill = ' ';
 | 
			
		||||
    }
 | 
			
		||||
    if (specs.precision >= 0 && arg.type() == type::cstring_type) {
 | 
			
		||||
      auto str = arg.visit(get_cstring<Char>());
 | 
			
		||||
      auto str_end = str + specs.precision;
 | 
			
		||||
      auto nul = std::find(str, str_end, Char());
 | 
			
		||||
      auto sv = basic_string_view<Char>(
 | 
			
		||||
          str, to_unsigned(nul != str_end ? nul - str : specs.precision));
 | 
			
		||||
      arg = make_arg<basic_printf_context<Char>>(sv);
 | 
			
		||||
    }
 | 
			
		||||
    if (specs.alt && arg.visit(is_zero_int())) specs.alt = false;
 | 
			
		||||
    if (specs.fill.template get<Char>() == '0') {
 | 
			
		||||
      if (arg.is_arithmetic() && specs.align != align::left)
 | 
			
		||||
        specs.align = align::numeric;
 | 
			
		||||
      else
 | 
			
		||||
        specs.fill = ' ';  // Ignore '0' flag for non-numeric types or if '-'
 | 
			
		||||
                           // flag is also present.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Parse length and convert the argument to the required type.
 | 
			
		||||
    c = it != end ? *it++ : 0;
 | 
			
		||||
    Char t = it != end ? *it : 0;
 | 
			
		||||
    switch (c) {
 | 
			
		||||
    case 'h':
 | 
			
		||||
      if (t == 'h') {
 | 
			
		||||
        ++it;
 | 
			
		||||
        t = it != end ? *it : 0;
 | 
			
		||||
        convert_arg<signed char>(arg, t);
 | 
			
		||||
      } else {
 | 
			
		||||
        convert_arg<short>(arg, t);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case 'l':
 | 
			
		||||
      if (t == 'l') {
 | 
			
		||||
        ++it;
 | 
			
		||||
        t = it != end ? *it : 0;
 | 
			
		||||
        convert_arg<long long>(arg, t);
 | 
			
		||||
      } else {
 | 
			
		||||
        convert_arg<long>(arg, t);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case 'j':
 | 
			
		||||
      convert_arg<intmax_t>(arg, t);
 | 
			
		||||
      break;
 | 
			
		||||
    case 'z':
 | 
			
		||||
      convert_arg<size_t>(arg, t);
 | 
			
		||||
      break;
 | 
			
		||||
    case 't':
 | 
			
		||||
      convert_arg<std::ptrdiff_t>(arg, t);
 | 
			
		||||
      break;
 | 
			
		||||
    case 'L':
 | 
			
		||||
      // printf produces garbage when 'L' is omitted for long double, no
 | 
			
		||||
      // need to do the same.
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      --it;
 | 
			
		||||
      convert_arg<void>(arg, c);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Parse type.
 | 
			
		||||
    if (it == end) report_error("invalid format string");
 | 
			
		||||
    char type = static_cast<char>(*it++);
 | 
			
		||||
    if (arg.is_integral()) {
 | 
			
		||||
      // Normalize type.
 | 
			
		||||
      switch (type) {
 | 
			
		||||
      case 'i':
 | 
			
		||||
      case 'u':
 | 
			
		||||
        type = 'd';
 | 
			
		||||
        break;
 | 
			
		||||
      case 'c':
 | 
			
		||||
        arg.visit(char_converter<basic_printf_context<Char>>(arg));
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    bool upper = false;
 | 
			
		||||
    specs.type = parse_printf_presentation_type(type, arg.type(), upper);
 | 
			
		||||
    if (specs.type == presentation_type::none)
 | 
			
		||||
      report_error("invalid format specifier");
 | 
			
		||||
    specs.upper = upper;
 | 
			
		||||
 | 
			
		||||
    start = it;
 | 
			
		||||
 | 
			
		||||
    // Format argument.
 | 
			
		||||
    arg.visit(printf_arg_formatter<Char>(out, specs, context));
 | 
			
		||||
  }
 | 
			
		||||
  write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
 | 
			
		||||
}
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
using printf_context = basic_printf_context<char>;
 | 
			
		||||
using wprintf_context = basic_printf_context<wchar_t>;
 | 
			
		||||
 | 
			
		||||
using printf_args = basic_format_args<printf_context>;
 | 
			
		||||
using wprintf_args = basic_format_args<wprintf_context>;
 | 
			
		||||
 | 
			
		||||
/// Constructs an `format_arg_store` object that contains references to
 | 
			
		||||
/// arguments and can be implicitly converted to `printf_args`.
 | 
			
		||||
template <typename Char = char, typename... T>
 | 
			
		||||
inline auto make_printf_args(T&... args)
 | 
			
		||||
    -> decltype(fmt::make_format_args<basic_printf_context<Char>>(args...)) {
 | 
			
		||||
  return fmt::make_format_args<basic_printf_context<Char>>(args...);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char> struct vprintf_args {
 | 
			
		||||
  using type = basic_format_args<basic_printf_context<Char>>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
inline auto vsprintf(basic_string_view<Char> fmt,
 | 
			
		||||
                     typename vprintf_args<Char>::type args)
 | 
			
		||||
    -> std::basic_string<Char> {
 | 
			
		||||
  auto buf = basic_memory_buffer<Char>();
 | 
			
		||||
  detail::vprintf(buf, fmt, args);
 | 
			
		||||
  return to_string(buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats `args` according to specifications in `fmt` and returns the result
 | 
			
		||||
 * as as string.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     std::string message = fmt::sprintf("The answer is %d", 42);
 | 
			
		||||
 */
 | 
			
		||||
template <typename S, typename... T, typename Char = char_t<S>>
 | 
			
		||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
 | 
			
		||||
  return vsprintf(detail::to_string_view(fmt),
 | 
			
		||||
                  fmt::make_format_args<basic_printf_context<Char>>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
 | 
			
		||||
                     typename vprintf_args<Char>::type args) -> int {
 | 
			
		||||
  auto buf = basic_memory_buffer<Char>();
 | 
			
		||||
  detail::vprintf(buf, fmt, args);
 | 
			
		||||
  size_t size = buf.size();
 | 
			
		||||
  return std::fwrite(buf.data(), sizeof(Char), size, f) < size
 | 
			
		||||
             ? -1
 | 
			
		||||
             : static_cast<int>(size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats `args` according to specifications in `fmt` and writes the output
 | 
			
		||||
 * to `f`.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     fmt::fprintf(stderr, "Don't %s!", "panic");
 | 
			
		||||
 */
 | 
			
		||||
template <typename S, typename... T, typename Char = char_t<S>>
 | 
			
		||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
 | 
			
		||||
  return vfprintf(f, detail::to_string_view(fmt),
 | 
			
		||||
                  make_printf_args<Char>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt,
 | 
			
		||||
                                   typename vprintf_args<Char>::type args)
 | 
			
		||||
    -> int {
 | 
			
		||||
  return vfprintf(stdout, fmt, args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats `args` according to specifications in `fmt` and writes the output
 | 
			
		||||
 * to `stdout`.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *   fmt::printf("Elapsed time: %.2f seconds", 1.23);
 | 
			
		||||
 */
 | 
			
		||||
template <typename... T>
 | 
			
		||||
inline auto printf(string_view fmt, const T&... args) -> int {
 | 
			
		||||
  return vfprintf(stdout, fmt, make_printf_args(args...));
 | 
			
		||||
}
 | 
			
		||||
template <typename... T>
 | 
			
		||||
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
 | 
			
		||||
                                  const T&... args) -> int {
 | 
			
		||||
  return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_END_EXPORT
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#endif  // FMT_PRINTF_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,882 @@
 | 
			
		|||
// Formatting library for C++ - range and tuple support
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_RANGES_H_
 | 
			
		||||
#define FMT_RANGES_H_
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <initializer_list>
 | 
			
		||||
#  include <iterator>
 | 
			
		||||
#  include <string>
 | 
			
		||||
#  include <tuple>
 | 
			
		||||
#  include <type_traits>
 | 
			
		||||
#  include <utility>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "format.h"
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
enum class range_format { disabled, map, set, sequence, string, debug_string };
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename T> class is_map {
 | 
			
		||||
  template <typename U> static auto check(U*) -> typename U::mapped_type;
 | 
			
		||||
  template <typename> static void check(...);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      !std::is_void<decltype(check<T>(nullptr))>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T> class is_set {
 | 
			
		||||
  template <typename U> static auto check(U*) -> typename U::key_type;
 | 
			
		||||
  template <typename> static void check(...);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename... Ts> struct conditional_helper {};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
 | 
			
		||||
 | 
			
		||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
 | 
			
		||||
 | 
			
		||||
#  define FMT_DECLTYPE_RETURN(val)  \
 | 
			
		||||
    ->decltype(val) { return val; } \
 | 
			
		||||
    static_assert(                  \
 | 
			
		||||
        true, "")  // This makes it so that a semicolon is required after the
 | 
			
		||||
                   // macro, which helps clang-format handle the formatting.
 | 
			
		||||
 | 
			
		||||
// C array overload
 | 
			
		||||
template <typename T, std::size_t N>
 | 
			
		||||
auto range_begin(const T (&arr)[N]) -> const T* {
 | 
			
		||||
  return arr;
 | 
			
		||||
}
 | 
			
		||||
template <typename T, std::size_t N>
 | 
			
		||||
auto range_end(const T (&arr)[N]) -> const T* {
 | 
			
		||||
  return arr + N;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Enable = void>
 | 
			
		||||
struct has_member_fn_begin_end_t : std::false_type {};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),
 | 
			
		||||
                                           decltype(std::declval<T>().end())>>
 | 
			
		||||
    : std::true_type {};
 | 
			
		||||
 | 
			
		||||
// Member function overloads.
 | 
			
		||||
template <typename T>
 | 
			
		||||
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
 | 
			
		||||
template <typename T>
 | 
			
		||||
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
 | 
			
		||||
 | 
			
		||||
// ADL overloads. Only participate in overload resolution if member functions
 | 
			
		||||
// are not found.
 | 
			
		||||
template <typename T>
 | 
			
		||||
auto range_begin(T&& rng)
 | 
			
		||||
    -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
 | 
			
		||||
                   decltype(begin(static_cast<T&&>(rng)))> {
 | 
			
		||||
  return begin(static_cast<T&&>(rng));
 | 
			
		||||
}
 | 
			
		||||
template <typename T>
 | 
			
		||||
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
 | 
			
		||||
                                       decltype(end(static_cast<T&&>(rng)))> {
 | 
			
		||||
  return end(static_cast<T&&>(rng));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Enable = void>
 | 
			
		||||
struct has_const_begin_end : std::false_type {};
 | 
			
		||||
template <typename T, typename Enable = void>
 | 
			
		||||
struct has_mutable_begin_end : std::false_type {};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct has_const_begin_end<
 | 
			
		||||
    T, void_t<decltype(*detail::range_begin(
 | 
			
		||||
                  std::declval<const remove_cvref_t<T>&>())),
 | 
			
		||||
              decltype(detail::range_end(
 | 
			
		||||
                  std::declval<const remove_cvref_t<T>&>()))>>
 | 
			
		||||
    : std::true_type {};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct has_mutable_begin_end<
 | 
			
		||||
    T, void_t<decltype(*detail::range_begin(std::declval<T&>())),
 | 
			
		||||
              decltype(detail::range_end(std::declval<T&>())),
 | 
			
		||||
              // the extra int here is because older versions of MSVC don't
 | 
			
		||||
              // SFINAE properly unless there are distinct types
 | 
			
		||||
              int>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct is_range_<T, void>
 | 
			
		||||
    : std::integral_constant<bool, (has_const_begin_end<T>::value ||
 | 
			
		||||
                                    has_mutable_begin_end<T>::value)> {};
 | 
			
		||||
#  undef FMT_DECLTYPE_RETURN
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// tuple_size and tuple_element check.
 | 
			
		||||
template <typename T> class is_tuple_like_ {
 | 
			
		||||
  template <typename U>
 | 
			
		||||
  static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
 | 
			
		||||
  template <typename> static void check(...);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      !std::is_void<decltype(check<T>(nullptr))>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Check for integer_sequence
 | 
			
		||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900
 | 
			
		||||
template <typename T, T... N>
 | 
			
		||||
using integer_sequence = std::integer_sequence<T, N...>;
 | 
			
		||||
template <size_t... N> using index_sequence = std::index_sequence<N...>;
 | 
			
		||||
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
 | 
			
		||||
#else
 | 
			
		||||
template <typename T, T... N> struct integer_sequence {
 | 
			
		||||
  using value_type = T;
 | 
			
		||||
 | 
			
		||||
  static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
 | 
			
		||||
 | 
			
		||||
template <typename T, size_t N, T... Ns>
 | 
			
		||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
 | 
			
		||||
template <typename T, T... Ns>
 | 
			
		||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
 | 
			
		||||
 | 
			
		||||
template <size_t N>
 | 
			
		||||
using make_index_sequence = make_integer_sequence<size_t, N>;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
 | 
			
		||||
 | 
			
		||||
template <typename T, typename C, bool = is_tuple_like_<T>::value>
 | 
			
		||||
class is_tuple_formattable_ {
 | 
			
		||||
 public:
 | 
			
		||||
  static constexpr const bool value = false;
 | 
			
		||||
};
 | 
			
		||||
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
 | 
			
		||||
  template <size_t... Is>
 | 
			
		||||
  static auto all_true(index_sequence<Is...>,
 | 
			
		||||
                       integer_sequence<bool, (Is >= 0)...>) -> std::true_type;
 | 
			
		||||
  static auto all_true(...) -> std::false_type;
 | 
			
		||||
 | 
			
		||||
  template <size_t... Is>
 | 
			
		||||
  static auto check(index_sequence<Is...>) -> decltype(all_true(
 | 
			
		||||
      index_sequence<Is...>{},
 | 
			
		||||
      integer_sequence<bool,
 | 
			
		||||
                       (is_formattable<typename std::tuple_element<Is, T>::type,
 | 
			
		||||
                                       C>::value)...>{}));
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      decltype(check(tuple_index_sequence<T>{}))::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Tuple, typename F, size_t... Is>
 | 
			
		||||
FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
 | 
			
		||||
  using std::get;
 | 
			
		||||
  // Using a free function get<Is>(Tuple) now.
 | 
			
		||||
  const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
 | 
			
		||||
  ignore_unused(unused);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Tuple, typename F>
 | 
			
		||||
FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
 | 
			
		||||
  for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
 | 
			
		||||
           std::forward<Tuple>(t), std::forward<F>(f));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
 | 
			
		||||
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
 | 
			
		||||
  using std::get;
 | 
			
		||||
  const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
 | 
			
		||||
  ignore_unused(unused);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Tuple1, typename Tuple2, typename F>
 | 
			
		||||
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
 | 
			
		||||
  for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
 | 
			
		||||
            std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
 | 
			
		||||
            std::forward<F>(f));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace tuple {
 | 
			
		||||
// Workaround a bug in MSVC 2019 (v140).
 | 
			
		||||
template <typename Char, typename... T>
 | 
			
		||||
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
 | 
			
		||||
 | 
			
		||||
using std::get;
 | 
			
		||||
template <typename Tuple, typename Char, std::size_t... Is>
 | 
			
		||||
auto get_formatters(index_sequence<Is...>)
 | 
			
		||||
    -> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
 | 
			
		||||
}  // namespace tuple
 | 
			
		||||
 | 
			
		||||
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
 | 
			
		||||
// Older MSVC doesn't get the reference type correctly for arrays.
 | 
			
		||||
template <typename R> struct range_reference_type_impl {
 | 
			
		||||
  using type = decltype(*detail::range_begin(std::declval<R&>()));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
 | 
			
		||||
  using type = T&;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
using range_reference_type = typename range_reference_type_impl<T>::type;
 | 
			
		||||
#else
 | 
			
		||||
template <typename Range>
 | 
			
		||||
using range_reference_type =
 | 
			
		||||
    decltype(*detail::range_begin(std::declval<Range&>()));
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// We don't use the Range's value_type for anything, but we do need the Range's
 | 
			
		||||
// reference type, with cv-ref stripped.
 | 
			
		||||
template <typename Range>
 | 
			
		||||
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
 | 
			
		||||
 | 
			
		||||
template <typename Formatter>
 | 
			
		||||
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
 | 
			
		||||
    -> decltype(f.set_debug_format(set)) {
 | 
			
		||||
  f.set_debug_format(set);
 | 
			
		||||
}
 | 
			
		||||
template <typename Formatter>
 | 
			
		||||
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct range_format_kind_
 | 
			
		||||
    : std::integral_constant<range_format,
 | 
			
		||||
                             std::is_same<uncvref_type<T>, T>::value
 | 
			
		||||
                                 ? range_format::disabled
 | 
			
		||||
                             : is_map<T>::value ? range_format::map
 | 
			
		||||
                             : is_set<T>::value ? range_format::set
 | 
			
		||||
                                                : range_format::sequence> {};
 | 
			
		||||
 | 
			
		||||
template <range_format K>
 | 
			
		||||
using range_format_constant = std::integral_constant<range_format, K>;
 | 
			
		||||
 | 
			
		||||
// These are not generic lambdas for compatibility with C++11.
 | 
			
		||||
template <typename ParseContext> struct parse_empty_specs {
 | 
			
		||||
  template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
 | 
			
		||||
    f.parse(ctx);
 | 
			
		||||
    detail::maybe_set_debug_format(f, true);
 | 
			
		||||
  }
 | 
			
		||||
  ParseContext& ctx;
 | 
			
		||||
};
 | 
			
		||||
template <typename FormatContext> struct format_tuple_element {
 | 
			
		||||
  using char_type = typename FormatContext::char_type;
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  void operator()(const formatter<T, char_type>& f, const T& v) {
 | 
			
		||||
    if (i > 0) ctx.advance_to(detail::copy<char_type>(separator, ctx.out()));
 | 
			
		||||
    ctx.advance_to(f.format(v, ctx));
 | 
			
		||||
    ++i;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int i;
 | 
			
		||||
  FormatContext& ctx;
 | 
			
		||||
  basic_string_view<char_type> separator;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
template <typename T> struct is_tuple_like {
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename C> struct is_tuple_formattable {
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      detail::is_tuple_formattable_<T, C>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Tuple, typename Char>
 | 
			
		||||
struct formatter<Tuple, Char,
 | 
			
		||||
                 enable_if_t<fmt::is_tuple_like<Tuple>::value &&
 | 
			
		||||
                             fmt::is_tuple_formattable<Tuple, Char>::value>> {
 | 
			
		||||
 private:
 | 
			
		||||
  decltype(detail::tuple::get_formatters<Tuple, Char>(
 | 
			
		||||
      detail::tuple_index_sequence<Tuple>())) formatters_;
 | 
			
		||||
 | 
			
		||||
  basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
 | 
			
		||||
  basic_string_view<Char> opening_bracket_ =
 | 
			
		||||
      detail::string_literal<Char, '('>{};
 | 
			
		||||
  basic_string_view<Char> closing_bracket_ =
 | 
			
		||||
      detail::string_literal<Char, ')'>{};
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_CONSTEXPR formatter() {}
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
 | 
			
		||||
    separator_ = sep;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
 | 
			
		||||
                                  basic_string_view<Char> close) {
 | 
			
		||||
    opening_bracket_ = open;
 | 
			
		||||
    closing_bracket_ = close;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    auto it = ctx.begin();
 | 
			
		||||
    if (it != ctx.end() && *it != '}') report_error("invalid format specifier");
 | 
			
		||||
    detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
 | 
			
		||||
    return it;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const Tuple& value, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    ctx.advance_to(detail::copy<Char>(opening_bracket_, ctx.out()));
 | 
			
		||||
    detail::for_each2(
 | 
			
		||||
        formatters_, value,
 | 
			
		||||
        detail::format_tuple_element<FormatContext>{0, ctx, separator_});
 | 
			
		||||
    return detail::copy<Char>(closing_bracket_, ctx.out());
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Char> struct is_range {
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
template <typename Context> struct range_mapper {
 | 
			
		||||
  using mapper = arg_mapper<Context>;
 | 
			
		||||
 | 
			
		||||
  template <typename T,
 | 
			
		||||
            FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
 | 
			
		||||
  static auto map(T&& value) -> T&& {
 | 
			
		||||
    return static_cast<T&&>(value);
 | 
			
		||||
  }
 | 
			
		||||
  template <typename T,
 | 
			
		||||
            FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
 | 
			
		||||
  static auto map(T&& value)
 | 
			
		||||
      -> decltype(mapper().map(static_cast<T&&>(value))) {
 | 
			
		||||
    return mapper().map(static_cast<T&&>(value));
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename Element>
 | 
			
		||||
using range_formatter_type =
 | 
			
		||||
    formatter<remove_cvref_t<decltype(range_mapper<buffered_context<Char>>{}
 | 
			
		||||
                                          .map(std::declval<Element>()))>,
 | 
			
		||||
              Char>;
 | 
			
		||||
 | 
			
		||||
template <typename R>
 | 
			
		||||
using maybe_const_range =
 | 
			
		||||
    conditional_t<has_const_begin_end<R>::value, const R, R>;
 | 
			
		||||
 | 
			
		||||
// Workaround a bug in MSVC 2015 and earlier.
 | 
			
		||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
 | 
			
		||||
template <typename R, typename Char>
 | 
			
		||||
struct is_formattable_delayed
 | 
			
		||||
    : is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
 | 
			
		||||
#endif
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
template <typename...> struct conjunction : std::true_type {};
 | 
			
		||||
template <typename P> struct conjunction<P> : P {};
 | 
			
		||||
template <typename P1, typename... Pn>
 | 
			
		||||
struct conjunction<P1, Pn...>
 | 
			
		||||
    : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Char, typename Enable = void>
 | 
			
		||||
struct range_formatter;
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Char>
 | 
			
		||||
struct range_formatter<
 | 
			
		||||
    T, Char,
 | 
			
		||||
    enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
 | 
			
		||||
                            is_formattable<T, Char>>::value>> {
 | 
			
		||||
 private:
 | 
			
		||||
  detail::range_formatter_type<Char, T> underlying_;
 | 
			
		||||
  basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
 | 
			
		||||
  basic_string_view<Char> opening_bracket_ =
 | 
			
		||||
      detail::string_literal<Char, '['>{};
 | 
			
		||||
  basic_string_view<Char> closing_bracket_ =
 | 
			
		||||
      detail::string_literal<Char, ']'>{};
 | 
			
		||||
  bool is_debug = false;
 | 
			
		||||
 | 
			
		||||
  template <typename Output, typename It, typename Sentinel, typename U = T,
 | 
			
		||||
            FMT_ENABLE_IF(std::is_same<U, Char>::value)>
 | 
			
		||||
  auto write_debug_string(Output& out, It it, Sentinel end) const -> Output {
 | 
			
		||||
    auto buf = basic_memory_buffer<Char>();
 | 
			
		||||
    for (; it != end; ++it) buf.push_back(*it);
 | 
			
		||||
    auto specs = format_specs();
 | 
			
		||||
    specs.type = presentation_type::debug;
 | 
			
		||||
    return detail::write<Char>(
 | 
			
		||||
        out, basic_string_view<Char>(buf.data(), buf.size()), specs);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename Output, typename It, typename Sentinel, typename U = T,
 | 
			
		||||
            FMT_ENABLE_IF(!std::is_same<U, Char>::value)>
 | 
			
		||||
  auto write_debug_string(Output& out, It, Sentinel) const -> Output {
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_CONSTEXPR range_formatter() {}
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
 | 
			
		||||
    return underlying_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
 | 
			
		||||
    separator_ = sep;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
 | 
			
		||||
                                  basic_string_view<Char> close) {
 | 
			
		||||
    opening_bracket_ = open;
 | 
			
		||||
    closing_bracket_ = close;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    auto it = ctx.begin();
 | 
			
		||||
    auto end = ctx.end();
 | 
			
		||||
    detail::maybe_set_debug_format(underlying_, true);
 | 
			
		||||
    if (it == end) return underlying_.parse(ctx);
 | 
			
		||||
 | 
			
		||||
    switch (detail::to_ascii(*it)) {
 | 
			
		||||
    case 'n':
 | 
			
		||||
      set_brackets({}, {});
 | 
			
		||||
      ++it;
 | 
			
		||||
      break;
 | 
			
		||||
    case '?':
 | 
			
		||||
      is_debug = true;
 | 
			
		||||
      set_brackets({}, {});
 | 
			
		||||
      ++it;
 | 
			
		||||
      if (it == end || *it != 's') report_error("invalid format specifier");
 | 
			
		||||
      FMT_FALLTHROUGH;
 | 
			
		||||
    case 's':
 | 
			
		||||
      if (!std::is_same<T, Char>::value)
 | 
			
		||||
        report_error("invalid format specifier");
 | 
			
		||||
      if (!is_debug) {
 | 
			
		||||
        set_brackets(detail::string_literal<Char, '"'>{},
 | 
			
		||||
                     detail::string_literal<Char, '"'>{});
 | 
			
		||||
        set_separator({});
 | 
			
		||||
        detail::maybe_set_debug_format(underlying_, false);
 | 
			
		||||
      }
 | 
			
		||||
      ++it;
 | 
			
		||||
      return it;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (it != end && *it != '}') {
 | 
			
		||||
      if (*it != ':') report_error("invalid format specifier");
 | 
			
		||||
      detail::maybe_set_debug_format(underlying_, false);
 | 
			
		||||
      ++it;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ctx.advance_to(it);
 | 
			
		||||
    return underlying_.parse(ctx);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename R, typename FormatContext>
 | 
			
		||||
  auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
 | 
			
		||||
    auto mapper = detail::range_mapper<buffered_context<Char>>();
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
    auto it = detail::range_begin(range);
 | 
			
		||||
    auto end = detail::range_end(range);
 | 
			
		||||
    if (is_debug) return write_debug_string(out, std::move(it), end);
 | 
			
		||||
 | 
			
		||||
    out = detail::copy<Char>(opening_bracket_, out);
 | 
			
		||||
    int i = 0;
 | 
			
		||||
    for (; it != end; ++it) {
 | 
			
		||||
      if (i > 0) out = detail::copy<Char>(separator_, out);
 | 
			
		||||
      ctx.advance_to(out);
 | 
			
		||||
      auto&& item = *it;  // Need an lvalue
 | 
			
		||||
      out = underlying_.format(mapper.map(item), ctx);
 | 
			
		||||
      ++i;
 | 
			
		||||
    }
 | 
			
		||||
    out = detail::copy<Char>(closing_bracket_, out);
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename T, typename Char, typename Enable = void>
 | 
			
		||||
struct range_format_kind
 | 
			
		||||
    : conditional_t<
 | 
			
		||||
          is_range<T, Char>::value, detail::range_format_kind_<T>,
 | 
			
		||||
          std::integral_constant<range_format, range_format::disabled>> {};
 | 
			
		||||
 | 
			
		||||
template <typename R, typename Char>
 | 
			
		||||
struct formatter<
 | 
			
		||||
    R, Char,
 | 
			
		||||
    enable_if_t<conjunction<
 | 
			
		||||
        bool_constant<
 | 
			
		||||
            range_format_kind<R, Char>::value != range_format::disabled &&
 | 
			
		||||
            range_format_kind<R, Char>::value != range_format::map &&
 | 
			
		||||
            range_format_kind<R, Char>::value != range_format::string &&
 | 
			
		||||
            range_format_kind<R, Char>::value != range_format::debug_string>
 | 
			
		||||
// Workaround a bug in MSVC 2015 and earlier.
 | 
			
		||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
 | 
			
		||||
        ,
 | 
			
		||||
        detail::is_formattable_delayed<R, Char>
 | 
			
		||||
#endif
 | 
			
		||||
        >::value>> {
 | 
			
		||||
 private:
 | 
			
		||||
  using range_type = detail::maybe_const_range<R>;
 | 
			
		||||
  range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  using nonlocking = void;
 | 
			
		||||
 | 
			
		||||
  FMT_CONSTEXPR formatter() {
 | 
			
		||||
    if (detail::const_check(range_format_kind<R, Char>::value !=
 | 
			
		||||
                            range_format::set))
 | 
			
		||||
      return;
 | 
			
		||||
    range_formatter_.set_brackets(detail::string_literal<Char, '{'>{},
 | 
			
		||||
                                  detail::string_literal<Char, '}'>{});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    return range_formatter_.parse(ctx);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(range_type& range, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    return range_formatter_.format(range, ctx);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A map formatter.
 | 
			
		||||
template <typename R, typename Char>
 | 
			
		||||
struct formatter<
 | 
			
		||||
    R, Char,
 | 
			
		||||
    enable_if_t<range_format_kind<R, Char>::value == range_format::map>> {
 | 
			
		||||
 private:
 | 
			
		||||
  using map_type = detail::maybe_const_range<R>;
 | 
			
		||||
  using element_type = detail::uncvref_type<map_type>;
 | 
			
		||||
 | 
			
		||||
  decltype(detail::tuple::get_formatters<element_type, Char>(
 | 
			
		||||
      detail::tuple_index_sequence<element_type>())) formatters_;
 | 
			
		||||
  bool no_delimiters_ = false;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_CONSTEXPR formatter() {}
 | 
			
		||||
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    auto it = ctx.begin();
 | 
			
		||||
    auto end = ctx.end();
 | 
			
		||||
    if (it != end) {
 | 
			
		||||
      if (detail::to_ascii(*it) == 'n') {
 | 
			
		||||
        no_delimiters_ = true;
 | 
			
		||||
        ++it;
 | 
			
		||||
      }
 | 
			
		||||
      if (it != end && *it != '}') {
 | 
			
		||||
        if (*it != ':') report_error("invalid format specifier");
 | 
			
		||||
        ++it;
 | 
			
		||||
      }
 | 
			
		||||
      ctx.advance_to(it);
 | 
			
		||||
    }
 | 
			
		||||
    detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
 | 
			
		||||
    return it;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) {
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
    basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
 | 
			
		||||
    if (!no_delimiters_) out = detail::copy<Char>(open, out);
 | 
			
		||||
    int i = 0;
 | 
			
		||||
    auto mapper = detail::range_mapper<buffered_context<Char>>();
 | 
			
		||||
    basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
 | 
			
		||||
    for (auto&& value : map) {
 | 
			
		||||
      if (i > 0) out = detail::copy<Char>(sep, out);
 | 
			
		||||
      ctx.advance_to(out);
 | 
			
		||||
      detail::for_each2(formatters_, mapper.map(value),
 | 
			
		||||
                        detail::format_tuple_element<FormatContext>{
 | 
			
		||||
                            0, ctx, detail::string_literal<Char, ':', ' '>{}});
 | 
			
		||||
      ++i;
 | 
			
		||||
    }
 | 
			
		||||
    basic_string_view<Char> close = detail::string_literal<Char, '}'>{};
 | 
			
		||||
    if (!no_delimiters_) out = detail::copy<Char>(close, out);
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A (debug_)string formatter.
 | 
			
		||||
template <typename R, typename Char>
 | 
			
		||||
struct formatter<
 | 
			
		||||
    R, Char,
 | 
			
		||||
    enable_if_t<range_format_kind<R, Char>::value == range_format::string ||
 | 
			
		||||
                range_format_kind<R, Char>::value ==
 | 
			
		||||
                    range_format::debug_string>> {
 | 
			
		||||
 private:
 | 
			
		||||
  using range_type = detail::maybe_const_range<R>;
 | 
			
		||||
  using string_type =
 | 
			
		||||
      conditional_t<std::is_constructible<
 | 
			
		||||
                        detail::std_string_view<Char>,
 | 
			
		||||
                        decltype(detail::range_begin(std::declval<R>())),
 | 
			
		||||
                        decltype(detail::range_end(std::declval<R>()))>::value,
 | 
			
		||||
                    detail::std_string_view<Char>, std::basic_string<Char>>;
 | 
			
		||||
 | 
			
		||||
  formatter<string_type, Char> underlying_;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    return underlying_.parse(ctx);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(range_type& range, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
    if (detail::const_check(range_format_kind<R, Char>::value ==
 | 
			
		||||
                            range_format::debug_string))
 | 
			
		||||
      *out++ = '"';
 | 
			
		||||
    out = underlying_.format(
 | 
			
		||||
        string_type{detail::range_begin(range), detail::range_end(range)}, ctx);
 | 
			
		||||
    if (detail::const_check(range_format_kind<R, Char>::value ==
 | 
			
		||||
                            range_format::debug_string))
 | 
			
		||||
      *out++ = '"';
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename It, typename Sentinel, typename Char = char>
 | 
			
		||||
struct join_view : detail::view {
 | 
			
		||||
  It begin;
 | 
			
		||||
  Sentinel end;
 | 
			
		||||
  basic_string_view<Char> sep;
 | 
			
		||||
 | 
			
		||||
  join_view(It b, Sentinel e, basic_string_view<Char> s)
 | 
			
		||||
      : begin(std::move(b)), end(e), sep(s) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename It, typename Sentinel, typename Char>
 | 
			
		||||
struct formatter<join_view<It, Sentinel, Char>, Char> {
 | 
			
		||||
 private:
 | 
			
		||||
  using value_type =
 | 
			
		||||
#ifdef __cpp_lib_ranges
 | 
			
		||||
      std::iter_value_t<It>;
 | 
			
		||||
#else
 | 
			
		||||
      typename std::iterator_traits<It>::value_type;
 | 
			
		||||
#endif
 | 
			
		||||
  formatter<remove_cvref_t<value_type>, Char> value_formatter_;
 | 
			
		||||
 | 
			
		||||
  using view_ref = conditional_t<std::is_copy_constructible<It>::value,
 | 
			
		||||
                                 const join_view<It, Sentinel, Char>&,
 | 
			
		||||
                                 join_view<It, Sentinel, Char>&&>;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  using nonlocking = void;
 | 
			
		||||
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
 | 
			
		||||
    return value_formatter_.parse(ctx);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(view_ref& value, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    auto it = std::forward<view_ref>(value).begin;
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
    if (it == value.end) return out;
 | 
			
		||||
    out = value_formatter_.format(*it, ctx);
 | 
			
		||||
    ++it;
 | 
			
		||||
    while (it != value.end) {
 | 
			
		||||
      out = detail::copy<Char>(value.sep.begin(), value.sep.end(), out);
 | 
			
		||||
      ctx.advance_to(out);
 | 
			
		||||
      out = value_formatter_.format(*it, ctx);
 | 
			
		||||
      ++it;
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Returns a view that formats the iterator range `[begin, end)` with elements
 | 
			
		||||
/// separated by `sep`.
 | 
			
		||||
template <typename It, typename Sentinel>
 | 
			
		||||
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
 | 
			
		||||
  return {std::move(begin), end, sep};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a view that formats `range` with elements separated by `sep`.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     auto v = std::vector<int>{1, 2, 3};
 | 
			
		||||
 *     fmt::print("{}", fmt::join(v, ", "));
 | 
			
		||||
 *     // Output: 1, 2, 3
 | 
			
		||||
 *
 | 
			
		||||
 * `fmt::join` applies passed format specifiers to the range elements:
 | 
			
		||||
 *
 | 
			
		||||
 *     fmt::print("{:02}", fmt::join(v, ", "));
 | 
			
		||||
 *     // Output: 01, 02, 03
 | 
			
		||||
 */
 | 
			
		||||
template <typename Range>
 | 
			
		||||
auto join(Range&& r, string_view sep)
 | 
			
		||||
    -> join_view<decltype(detail::range_begin(r)),
 | 
			
		||||
                 decltype(detail::range_end(r))> {
 | 
			
		||||
  return {detail::range_begin(r), detail::range_end(r), sep};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename... T> struct tuple_join_view : detail::view {
 | 
			
		||||
  const std::tuple<T...>& tuple;
 | 
			
		||||
  basic_string_view<Char> sep;
 | 
			
		||||
 | 
			
		||||
  tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
 | 
			
		||||
      : tuple(t), sep{s} {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
 | 
			
		||||
// support in tuple_join. It is disabled by default because of issues with
 | 
			
		||||
// the dynamic width and precision.
 | 
			
		||||
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
 | 
			
		||||
#  define FMT_TUPLE_JOIN_SPECIFIERS 0
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename... T>
 | 
			
		||||
struct formatter<tuple_join_view<Char, T...>, Char> {
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const tuple_join_view<Char, T...>& value,
 | 
			
		||||
              FormatContext& ctx) const -> typename FormatContext::iterator {
 | 
			
		||||
    return do_format(value, ctx,
 | 
			
		||||
                     std::integral_constant<size_t, sizeof...(T)>());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
 | 
			
		||||
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
 | 
			
		||||
                              std::integral_constant<size_t, 0>)
 | 
			
		||||
      -> decltype(ctx.begin()) {
 | 
			
		||||
    return ctx.begin();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename ParseContext, size_t N>
 | 
			
		||||
  FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
 | 
			
		||||
                              std::integral_constant<size_t, N>)
 | 
			
		||||
      -> decltype(ctx.begin()) {
 | 
			
		||||
    auto end = ctx.begin();
 | 
			
		||||
#if FMT_TUPLE_JOIN_SPECIFIERS
 | 
			
		||||
    end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
 | 
			
		||||
    if (N > 1) {
 | 
			
		||||
      auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
 | 
			
		||||
      if (end != end1)
 | 
			
		||||
        report_error("incompatible format specs for tuple elements");
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
    return end;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
 | 
			
		||||
                 std::integral_constant<size_t, 0>) const ->
 | 
			
		||||
      typename FormatContext::iterator {
 | 
			
		||||
    return ctx.out();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext, size_t N>
 | 
			
		||||
  auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
 | 
			
		||||
                 std::integral_constant<size_t, N>) const ->
 | 
			
		||||
      typename FormatContext::iterator {
 | 
			
		||||
    auto out = std::get<sizeof...(T) - N>(formatters_)
 | 
			
		||||
                   .format(std::get<sizeof...(T) - N>(value.tuple), ctx);
 | 
			
		||||
    if (N <= 1) return out;
 | 
			
		||||
    out = detail::copy<Char>(value.sep, out);
 | 
			
		||||
    ctx.advance_to(out);
 | 
			
		||||
    return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
// Check if T has an interface like a container adaptor (e.g. std::stack,
 | 
			
		||||
// std::queue, std::priority_queue).
 | 
			
		||||
template <typename T> class is_container_adaptor_like {
 | 
			
		||||
  template <typename U> static auto check(U* p) -> typename U::container_type;
 | 
			
		||||
  template <typename> static void check(...);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      !std::is_void<decltype(check<T>(nullptr))>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Container> struct all {
 | 
			
		||||
  const Container& c;
 | 
			
		||||
  auto begin() const -> typename Container::const_iterator { return c.begin(); }
 | 
			
		||||
  auto end() const -> typename Container::const_iterator { return c.end(); }
 | 
			
		||||
};
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Char>
 | 
			
		||||
struct formatter<
 | 
			
		||||
    T, Char,
 | 
			
		||||
    enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
 | 
			
		||||
                            bool_constant<range_format_kind<T, Char>::value ==
 | 
			
		||||
                                          range_format::disabled>>::value>>
 | 
			
		||||
    : formatter<detail::all<typename T::container_type>, Char> {
 | 
			
		||||
  using all = detail::all<typename T::container_type>;
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
 | 
			
		||||
    struct getter : T {
 | 
			
		||||
      static auto get(const T& t) -> all {
 | 
			
		||||
        return {t.*(&getter::c)};  // Access c through the derived class.
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    return formatter<all>::format(getter::get(t), ctx);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_EXPORT
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns an object that formats `std::tuple` with elements separated by `sep`.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     auto t = std::tuple<int, char>{1, 'a'};
 | 
			
		||||
 *     fmt::print("{}", fmt::join(t, ", "));
 | 
			
		||||
 *     // Output: 1, a
 | 
			
		||||
 */
 | 
			
		||||
template <typename... T>
 | 
			
		||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
 | 
			
		||||
    -> tuple_join_view<char, T...> {
 | 
			
		||||
  return {tuple, sep};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns an object that formats `std::initializer_list` with elements
 | 
			
		||||
 * separated by `sep`.
 | 
			
		||||
 *
 | 
			
		||||
 * **Example**:
 | 
			
		||||
 *
 | 
			
		||||
 *     fmt::print("{}", fmt::join({1, 2, 3}, ", "));
 | 
			
		||||
 *     // Output: "1, 2, 3"
 | 
			
		||||
 */
 | 
			
		||||
template <typename T>
 | 
			
		||||
auto join(std::initializer_list<T> list, string_view sep)
 | 
			
		||||
    -> join_view<const T*, const T*> {
 | 
			
		||||
  return join(std::begin(list), std::end(list), sep);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_END_EXPORT
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#endif  // FMT_RANGES_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,699 @@
 | 
			
		|||
// Formatting library for C++ - formatters for standard library types
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - present, Victor Zverovich
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_STD_H_
 | 
			
		||||
#define FMT_STD_H_
 | 
			
		||||
 | 
			
		||||
#include "format.h"
 | 
			
		||||
#include "ostream.h"
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <atomic>
 | 
			
		||||
#  include <bitset>
 | 
			
		||||
#  include <complex>
 | 
			
		||||
#  include <cstdlib>
 | 
			
		||||
#  include <exception>
 | 
			
		||||
#  include <memory>
 | 
			
		||||
#  include <thread>
 | 
			
		||||
#  include <type_traits>
 | 
			
		||||
#  include <typeinfo>
 | 
			
		||||
#  include <utility>
 | 
			
		||||
#  include <vector>
 | 
			
		||||
 | 
			
		||||
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
 | 
			
		||||
#  if FMT_CPLUSPLUS >= 201703L
 | 
			
		||||
#    if FMT_HAS_INCLUDE(<filesystem>)
 | 
			
		||||
#      include <filesystem>
 | 
			
		||||
#    endif
 | 
			
		||||
#    if FMT_HAS_INCLUDE(<variant>)
 | 
			
		||||
#      include <variant>
 | 
			
		||||
#    endif
 | 
			
		||||
#    if FMT_HAS_INCLUDE(<optional>)
 | 
			
		||||
#      include <optional>
 | 
			
		||||
#    endif
 | 
			
		||||
#  endif
 | 
			
		||||
// Use > instead of >= in the version check because <source_location> may be
 | 
			
		||||
// available after C++17 but before C++20 is marked as implemented.
 | 
			
		||||
#  if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)
 | 
			
		||||
#    include <source_location>
 | 
			
		||||
#  endif
 | 
			
		||||
#  if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE(<expected>)
 | 
			
		||||
#    include <expected>
 | 
			
		||||
#  endif
 | 
			
		||||
#endif  // FMT_MODULE
 | 
			
		||||
 | 
			
		||||
#if FMT_HAS_INCLUDE(<version>)
 | 
			
		||||
#  include <version>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// GCC 4 does not support FMT_HAS_INCLUDE.
 | 
			
		||||
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
 | 
			
		||||
#  include <cxxabi.h>
 | 
			
		||||
// Android NDK with gabi++ library on some architectures does not implement
 | 
			
		||||
// abi::__cxa_demangle().
 | 
			
		||||
#  ifndef __GABIXX_CXXABI_H__
 | 
			
		||||
#    define FMT_HAS_ABI_CXA_DEMANGLE
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
 | 
			
		||||
#ifndef FMT_CPP_LIB_FILESYSTEM
 | 
			
		||||
#  ifdef __cpp_lib_filesystem
 | 
			
		||||
#    define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
 | 
			
		||||
#  else
 | 
			
		||||
#    define FMT_CPP_LIB_FILESYSTEM 0
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_CPP_LIB_VARIANT
 | 
			
		||||
#  ifdef __cpp_lib_variant
 | 
			
		||||
#    define FMT_CPP_LIB_VARIANT __cpp_lib_variant
 | 
			
		||||
#  else
 | 
			
		||||
#    define FMT_CPP_LIB_VARIANT 0
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if FMT_CPP_LIB_FILESYSTEM
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename PathChar>
 | 
			
		||||
auto get_path_string(const std::filesystem::path& p,
 | 
			
		||||
                     const std::basic_string<PathChar>& native) {
 | 
			
		||||
  if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
 | 
			
		||||
    return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
 | 
			
		||||
  else
 | 
			
		||||
    return p.string<Char>();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename PathChar>
 | 
			
		||||
void write_escaped_path(basic_memory_buffer<Char>& quoted,
 | 
			
		||||
                        const std::filesystem::path& p,
 | 
			
		||||
                        const std::basic_string<PathChar>& native) {
 | 
			
		||||
  if constexpr (std::is_same_v<Char, char> &&
 | 
			
		||||
                std::is_same_v<PathChar, wchar_t>) {
 | 
			
		||||
    auto buf = basic_memory_buffer<wchar_t>();
 | 
			
		||||
    write_escaped_string<wchar_t>(std::back_inserter(buf), native);
 | 
			
		||||
    bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
 | 
			
		||||
    FMT_ASSERT(valid, "invalid utf16");
 | 
			
		||||
  } else if constexpr (std::is_same_v<Char, PathChar>) {
 | 
			
		||||
    write_escaped_string<std::filesystem::path::value_type>(
 | 
			
		||||
        std::back_inserter(quoted), native);
 | 
			
		||||
  } else {
 | 
			
		||||
    write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename Char> struct formatter<std::filesystem::path, Char> {
 | 
			
		||||
 private:
 | 
			
		||||
  format_specs specs_;
 | 
			
		||||
  detail::arg_ref<Char> width_ref_;
 | 
			
		||||
  bool debug_ = false;
 | 
			
		||||
  char path_type_ = 0;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
 | 
			
		||||
 | 
			
		||||
  template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
 | 
			
		||||
    auto it = ctx.begin(), end = ctx.end();
 | 
			
		||||
    if (it == end) return it;
 | 
			
		||||
 | 
			
		||||
    it = detail::parse_align(it, end, specs_);
 | 
			
		||||
    if (it == end) return it;
 | 
			
		||||
 | 
			
		||||
    it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
 | 
			
		||||
    if (it != end && *it == '?') {
 | 
			
		||||
      debug_ = true;
 | 
			
		||||
      ++it;
 | 
			
		||||
    }
 | 
			
		||||
    if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++);
 | 
			
		||||
    return it;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::filesystem::path& p, FormatContext& ctx) const {
 | 
			
		||||
    auto specs = specs_;
 | 
			
		||||
    auto path_string =
 | 
			
		||||
        !path_type_ ? p.native()
 | 
			
		||||
                    : p.generic_string<std::filesystem::path::value_type>();
 | 
			
		||||
 | 
			
		||||
    detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
 | 
			
		||||
                                                       ctx);
 | 
			
		||||
    if (!debug_) {
 | 
			
		||||
      auto s = detail::get_path_string<Char>(p, path_string);
 | 
			
		||||
      return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
 | 
			
		||||
    }
 | 
			
		||||
    auto quoted = basic_memory_buffer<Char>();
 | 
			
		||||
    detail::write_escaped_path(quoted, p, path_string);
 | 
			
		||||
    return detail::write(ctx.out(),
 | 
			
		||||
                         basic_string_view<Char>(quoted.data(), quoted.size()),
 | 
			
		||||
                         specs);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class path : public std::filesystem::path {
 | 
			
		||||
 public:
 | 
			
		||||
  auto display_string() const -> std::string {
 | 
			
		||||
    const std::filesystem::path& base = *this;
 | 
			
		||||
    return fmt::format(FMT_STRING("{}"), base);
 | 
			
		||||
  }
 | 
			
		||||
  auto system_string() const -> std::string { return string(); }
 | 
			
		||||
 | 
			
		||||
  auto generic_display_string() const -> std::string {
 | 
			
		||||
    const std::filesystem::path& base = *this;
 | 
			
		||||
    return fmt::format(FMT_STRING("{:g}"), base);
 | 
			
		||||
  }
 | 
			
		||||
  auto generic_system_string() const -> std::string { return generic_string(); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
#endif  // FMT_CPP_LIB_FILESYSTEM
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <std::size_t N, typename Char>
 | 
			
		||||
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
 | 
			
		||||
 private:
 | 
			
		||||
  // Functor because C++11 doesn't support generic lambdas.
 | 
			
		||||
  struct writer {
 | 
			
		||||
    const std::bitset<N>& bs;
 | 
			
		||||
 | 
			
		||||
    template <typename OutputIt>
 | 
			
		||||
    FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
 | 
			
		||||
      for (auto pos = N; pos > 0; --pos) {
 | 
			
		||||
        out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return out;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::bitset<N>& bs, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    return write_padded(ctx, writer{bs});
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename Char>
 | 
			
		||||
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#ifdef __cpp_lib_optional
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename T, typename Char>
 | 
			
		||||
struct formatter<std::optional<T>, Char,
 | 
			
		||||
                 std::enable_if_t<is_formattable<T, Char>::value>> {
 | 
			
		||||
 private:
 | 
			
		||||
  formatter<T, Char> underlying_;
 | 
			
		||||
  static constexpr basic_string_view<Char> optional =
 | 
			
		||||
      detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
 | 
			
		||||
                             '('>{};
 | 
			
		||||
  static constexpr basic_string_view<Char> none =
 | 
			
		||||
      detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
 | 
			
		||||
 | 
			
		||||
  template <class U>
 | 
			
		||||
  FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
 | 
			
		||||
      -> decltype(u.set_debug_format(set)) {
 | 
			
		||||
    u.set_debug_format(set);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <class U>
 | 
			
		||||
  FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
 | 
			
		||||
    maybe_set_debug_format(underlying_, true);
 | 
			
		||||
    return underlying_.parse(ctx);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::optional<T>& opt, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    if (!opt) return detail::write<Char>(ctx.out(), none);
 | 
			
		||||
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
    out = detail::write<Char>(out, optional);
 | 
			
		||||
    ctx.advance_to(out);
 | 
			
		||||
    out = underlying_.format(*opt, ctx);
 | 
			
		||||
    return detail::write(out, ')');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
#endif  // __cpp_lib_optional
 | 
			
		||||
 | 
			
		||||
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename OutputIt, typename T>
 | 
			
		||||
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
 | 
			
		||||
  if constexpr (has_to_string_view<T>::value)
 | 
			
		||||
    return write_escaped_string<Char>(out, detail::to_string_view(v));
 | 
			
		||||
  if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
 | 
			
		||||
  return write<Char>(out, v);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef __cpp_lib_expected
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename T, typename E, typename Char>
 | 
			
		||||
struct formatter<std::expected<T, E>, Char,
 | 
			
		||||
                 std::enable_if_t<is_formattable<T, Char>::value &&
 | 
			
		||||
                                  is_formattable<E, Char>::value>> {
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    return ctx.begin();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::expected<T, E>& value, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
 | 
			
		||||
    if (value.has_value()) {
 | 
			
		||||
      out = detail::write<Char>(out, "expected(");
 | 
			
		||||
      out = detail::write_escaped_alternative<Char>(out, *value);
 | 
			
		||||
    } else {
 | 
			
		||||
      out = detail::write<Char>(out, "unexpected(");
 | 
			
		||||
      out = detail::write_escaped_alternative<Char>(out, value.error());
 | 
			
		||||
    }
 | 
			
		||||
    *out++ = ')';
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
#endif  // __cpp_lib_expected
 | 
			
		||||
 | 
			
		||||
#ifdef __cpp_lib_source_location
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <> struct formatter<std::source_location> {
 | 
			
		||||
  template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
 | 
			
		||||
    return ctx.begin();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::source_location& loc, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
    out = detail::write(out, loc.file_name());
 | 
			
		||||
    out = detail::write(out, ':');
 | 
			
		||||
    out = detail::write<char>(out, loc.line());
 | 
			
		||||
    out = detail::write(out, ':');
 | 
			
		||||
    out = detail::write<char>(out, loc.column());
 | 
			
		||||
    out = detail::write(out, ": ");
 | 
			
		||||
    out = detail::write(out, loc.function_name());
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if FMT_CPP_LIB_VARIANT
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
using variant_index_sequence =
 | 
			
		||||
    std::make_index_sequence<std::variant_size<T>::value>;
 | 
			
		||||
 | 
			
		||||
template <typename> struct is_variant_like_ : std::false_type {};
 | 
			
		||||
template <typename... Types>
 | 
			
		||||
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
// formattable element check.
 | 
			
		||||
template <typename T, typename C> class is_variant_formattable_ {
 | 
			
		||||
  template <std::size_t... Is>
 | 
			
		||||
  static std::conjunction<
 | 
			
		||||
      is_formattable<std::variant_alternative_t<Is, T>, C>...>
 | 
			
		||||
      check(std::index_sequence<Is...>);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      decltype(check(variant_index_sequence<T>{}))::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
template <typename T> struct is_variant_like {
 | 
			
		||||
  static constexpr const bool value = detail::is_variant_like_<T>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename C> struct is_variant_formattable {
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      detail::is_variant_formattable_<T, C>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename Char> struct formatter<std::monostate, Char> {
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    return ctx.begin();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::monostate&, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    return detail::write<Char>(ctx.out(), "monostate");
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename Variant, typename Char>
 | 
			
		||||
struct formatter<
 | 
			
		||||
    Variant, Char,
 | 
			
		||||
    std::enable_if_t<std::conjunction_v<
 | 
			
		||||
        is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    return ctx.begin();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const Variant& value, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
 | 
			
		||||
    out = detail::write<Char>(out, "variant(");
 | 
			
		||||
    FMT_TRY {
 | 
			
		||||
      std::visit(
 | 
			
		||||
          [&](const auto& v) {
 | 
			
		||||
            out = detail::write_escaped_alternative<Char>(out, v);
 | 
			
		||||
          },
 | 
			
		||||
          value);
 | 
			
		||||
    }
 | 
			
		||||
    FMT_CATCH(const std::bad_variant_access&) {
 | 
			
		||||
      detail::write<Char>(out, "valueless by exception");
 | 
			
		||||
    }
 | 
			
		||||
    *out++ = ')';
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
#endif  // FMT_CPP_LIB_VARIANT
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename Char> struct formatter<std::error_code, Char> {
 | 
			
		||||
  template <typename ParseContext>
 | 
			
		||||
  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
 | 
			
		||||
    return ctx.begin();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
    out = detail::write_bytes<Char>(out, ec.category().name(), format_specs());
 | 
			
		||||
    out = detail::write<Char>(out, Char(':'));
 | 
			
		||||
    out = detail::write<Char>(out, ec.value());
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if FMT_USE_RTTI
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename Char, typename OutputIt>
 | 
			
		||||
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
 | 
			
		||||
#  ifdef FMT_HAS_ABI_CXA_DEMANGLE
 | 
			
		||||
  int status = 0;
 | 
			
		||||
  std::size_t size = 0;
 | 
			
		||||
  std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
 | 
			
		||||
      abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
 | 
			
		||||
 | 
			
		||||
  string_view demangled_name_view;
 | 
			
		||||
  if (demangled_name_ptr) {
 | 
			
		||||
    demangled_name_view = demangled_name_ptr.get();
 | 
			
		||||
 | 
			
		||||
    // Normalization of stdlib inline namespace names.
 | 
			
		||||
    // libc++ inline namespaces.
 | 
			
		||||
    //  std::__1::*       -> std::*
 | 
			
		||||
    //  std::__1::__fs::* -> std::*
 | 
			
		||||
    // libstdc++ inline namespaces.
 | 
			
		||||
    //  std::__cxx11::*             -> std::*
 | 
			
		||||
    //  std::filesystem::__cxx11::* -> std::filesystem::*
 | 
			
		||||
    if (demangled_name_view.starts_with("std::")) {
 | 
			
		||||
      char* begin = demangled_name_ptr.get();
 | 
			
		||||
      char* to = begin + 5;  // std::
 | 
			
		||||
      for (char *from = to, *end = begin + demangled_name_view.size();
 | 
			
		||||
           from < end;) {
 | 
			
		||||
        // This is safe, because demangled_name is NUL-terminated.
 | 
			
		||||
        if (from[0] == '_' && from[1] == '_') {
 | 
			
		||||
          char* next = from + 1;
 | 
			
		||||
          while (next < end && *next != ':') next++;
 | 
			
		||||
          if (next[0] == ':' && next[1] == ':') {
 | 
			
		||||
            from = next + 2;
 | 
			
		||||
            continue;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        *to++ = *from++;
 | 
			
		||||
      }
 | 
			
		||||
      demangled_name_view = {begin, detail::to_unsigned(to - begin)};
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    demangled_name_view = string_view(ti.name());
 | 
			
		||||
  }
 | 
			
		||||
  return detail::write_bytes<Char>(out, demangled_name_view);
 | 
			
		||||
#  elif FMT_MSC_VERSION
 | 
			
		||||
  const string_view demangled_name(ti.name());
 | 
			
		||||
  for (std::size_t i = 0; i < demangled_name.size(); ++i) {
 | 
			
		||||
    auto sub = demangled_name;
 | 
			
		||||
    sub.remove_prefix(i);
 | 
			
		||||
    if (sub.starts_with("enum ")) {
 | 
			
		||||
      i += 4;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (sub.starts_with("class ") || sub.starts_with("union ")) {
 | 
			
		||||
      i += 5;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (sub.starts_with("struct ")) {
 | 
			
		||||
      i += 6;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (*sub.begin() != ' ') *out++ = *sub.begin();
 | 
			
		||||
  }
 | 
			
		||||
  return out;
 | 
			
		||||
#  else
 | 
			
		||||
  return detail::write_bytes<Char>(out, string_view(ti.name()));
 | 
			
		||||
#  endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename Char>
 | 
			
		||||
struct formatter<std::type_info, Char  // DEPRECATED! Mixing code unit types.
 | 
			
		||||
                 > {
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
 | 
			
		||||
      -> decltype(ctx.begin()) {
 | 
			
		||||
    return ctx.begin();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename Context>
 | 
			
		||||
  auto format(const std::type_info& ti, Context& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    return detail::write_demangled_name<Char>(ctx.out(), ti);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename T, typename Char>
 | 
			
		||||
struct formatter<
 | 
			
		||||
    T, Char,  // DEPRECATED! Mixing code unit types.
 | 
			
		||||
    typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
 | 
			
		||||
 private:
 | 
			
		||||
  bool with_typename_ = false;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
 | 
			
		||||
      -> decltype(ctx.begin()) {
 | 
			
		||||
    auto it = ctx.begin();
 | 
			
		||||
    auto end = ctx.end();
 | 
			
		||||
    if (it == end || *it == '}') return it;
 | 
			
		||||
    if (*it == 't') {
 | 
			
		||||
      ++it;
 | 
			
		||||
      with_typename_ = FMT_USE_RTTI != 0;
 | 
			
		||||
    }
 | 
			
		||||
    return it;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename Context>
 | 
			
		||||
  auto format(const std::exception& ex, Context& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    auto out = ctx.out();
 | 
			
		||||
#if FMT_USE_RTTI
 | 
			
		||||
    if (with_typename_) {
 | 
			
		||||
      out = detail::write_demangled_name<Char>(out, typeid(ex));
 | 
			
		||||
      *out++ = ':';
 | 
			
		||||
      *out++ = ' ';
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
    return detail::write_bytes<Char>(out, string_view(ex.what()));
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Enable = void>
 | 
			
		||||
struct has_flip : std::false_type {};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
 | 
			
		||||
    : std::true_type {};
 | 
			
		||||
 | 
			
		||||
template <typename T> struct is_bit_reference_like {
 | 
			
		||||
  static constexpr const bool value =
 | 
			
		||||
      std::is_convertible<T, bool>::value &&
 | 
			
		||||
      std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifdef _LIBCPP_VERSION
 | 
			
		||||
 | 
			
		||||
// Workaround for libc++ incompatibility with C++ standard.
 | 
			
		||||
// According to the Standard, `bitset::operator[] const` returns bool.
 | 
			
		||||
template <typename C>
 | 
			
		||||
struct is_bit_reference_like<std::__bit_const_reference<C>> {
 | 
			
		||||
  static constexpr const bool value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
// We can't use std::vector<bool, Allocator>::reference and
 | 
			
		||||
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
 | 
			
		||||
// in partial specialization.
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename BitRef, typename Char>
 | 
			
		||||
struct formatter<BitRef, Char,
 | 
			
		||||
                 enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
 | 
			
		||||
    : formatter<bool, Char> {
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    return formatter<bool, Char>::format(v, ctx);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename Deleter>
 | 
			
		||||
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
 | 
			
		||||
  return p.get();
 | 
			
		||||
}
 | 
			
		||||
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
 | 
			
		||||
  return p.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename T, typename Char>
 | 
			
		||||
struct formatter<std::atomic<T>, Char,
 | 
			
		||||
                 enable_if_t<is_formattable<T, Char>::value>>
 | 
			
		||||
    : formatter<T, Char> {
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::atomic<T>& v, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    return formatter<T, Char>::format(v.load(), ctx);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifdef __cpp_lib_atomic_flag_test
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename Char>
 | 
			
		||||
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::atomic_flag& v, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    return formatter<bool, Char>::format(v.test(), ctx);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
#endif  // __cpp_lib_atomic_flag_test
 | 
			
		||||
 | 
			
		||||
FMT_EXPORT
 | 
			
		||||
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
 | 
			
		||||
 private:
 | 
			
		||||
  detail::dynamic_format_specs<Char> specs_;
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext, typename OutputIt>
 | 
			
		||||
  FMT_CONSTEXPR auto do_format(const std::complex<T>& c,
 | 
			
		||||
                               detail::dynamic_format_specs<Char>& specs,
 | 
			
		||||
                               FormatContext& ctx, OutputIt out) const
 | 
			
		||||
      -> OutputIt {
 | 
			
		||||
    if (c.real() != 0) {
 | 
			
		||||
      *out++ = Char('(');
 | 
			
		||||
      out = detail::write<Char>(out, c.real(), specs, ctx.locale());
 | 
			
		||||
      specs.sign = sign::plus;
 | 
			
		||||
      out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
 | 
			
		||||
      if (!detail::isfinite(c.imag())) *out++ = Char(' ');
 | 
			
		||||
      *out++ = Char('i');
 | 
			
		||||
      *out++ = Char(')');
 | 
			
		||||
      return out;
 | 
			
		||||
    }
 | 
			
		||||
    out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
 | 
			
		||||
    if (!detail::isfinite(c.imag())) *out++ = Char(' ');
 | 
			
		||||
    *out++ = Char('i');
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
 | 
			
		||||
      -> decltype(ctx.begin()) {
 | 
			
		||||
    if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
 | 
			
		||||
    return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
 | 
			
		||||
                              detail::type_constant<T, Char>::value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename FormatContext>
 | 
			
		||||
  auto format(const std::complex<T>& c, FormatContext& ctx) const
 | 
			
		||||
      -> decltype(ctx.out()) {
 | 
			
		||||
    auto specs = specs_;
 | 
			
		||||
    if (specs.width_ref.kind != detail::arg_id_kind::none ||
 | 
			
		||||
        specs.precision_ref.kind != detail::arg_id_kind::none) {
 | 
			
		||||
      detail::handle_dynamic_spec<detail::width_checker>(specs.width,
 | 
			
		||||
                                                         specs.width_ref, ctx);
 | 
			
		||||
      detail::handle_dynamic_spec<detail::precision_checker>(
 | 
			
		||||
          specs.precision, specs.precision_ref, ctx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
 | 
			
		||||
    auto buf = basic_memory_buffer<Char>();
 | 
			
		||||
 | 
			
		||||
    auto outer_specs = format_specs();
 | 
			
		||||
    outer_specs.width = specs.width;
 | 
			
		||||
    outer_specs.fill = specs.fill;
 | 
			
		||||
    outer_specs.align = specs.align;
 | 
			
		||||
 | 
			
		||||
    specs.width = 0;
 | 
			
		||||
    specs.fill = {};
 | 
			
		||||
    specs.align = align::none;
 | 
			
		||||
 | 
			
		||||
    do_format(c, specs, ctx, basic_appender<Char>(buf));
 | 
			
		||||
    return detail::write<Char>(ctx.out(),
 | 
			
		||||
                               basic_string_view<Char>(buf.data(), buf.size()),
 | 
			
		||||
                               outer_specs);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
#endif  // FMT_STD_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,322 @@
 | 
			
		|||
// Formatting library for C++ - optional wchar_t and exotic character support
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - present, Victor Zverovich
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_XCHAR_H_
 | 
			
		||||
#define FMT_XCHAR_H_
 | 
			
		||||
 | 
			
		||||
#include "color.h"
 | 
			
		||||
#include "format.h"
 | 
			
		||||
#include "ranges.h"
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <cwchar>
 | 
			
		||||
#  if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
 | 
			
		||||
#    include <locale>
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
 | 
			
		||||
 | 
			
		||||
template <typename S, typename = void> struct format_string_char {};
 | 
			
		||||
 | 
			
		||||
template <typename S>
 | 
			
		||||
struct format_string_char<
 | 
			
		||||
    S, void_t<decltype(sizeof(detail::to_string_view(std::declval<S>())))>> {
 | 
			
		||||
  using type = char_t<S>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename S>
 | 
			
		||||
struct format_string_char<S, enable_if_t<is_compile_string<S>::value>> {
 | 
			
		||||
  using type = typename S::char_type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename S>
 | 
			
		||||
using format_string_char_t = typename format_string_char<S>::type;
 | 
			
		||||
 | 
			
		||||
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
 | 
			
		||||
                      const format_specs& specs, locale_ref loc) -> bool {
 | 
			
		||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
 | 
			
		||||
  auto& numpunct =
 | 
			
		||||
      std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
 | 
			
		||||
  auto separator = std::wstring();
 | 
			
		||||
  auto grouping = numpunct.grouping();
 | 
			
		||||
  if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
 | 
			
		||||
  return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
 | 
			
		||||
#endif
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_EXPORT
 | 
			
		||||
 | 
			
		||||
using wstring_view = basic_string_view<wchar_t>;
 | 
			
		||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
 | 
			
		||||
using wformat_context = buffered_context<wchar_t>;
 | 
			
		||||
using wformat_args = basic_format_args<wformat_context>;
 | 
			
		||||
using wmemory_buffer = basic_memory_buffer<wchar_t>;
 | 
			
		||||
 | 
			
		||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
 | 
			
		||||
// Workaround broken conversion on older gcc.
 | 
			
		||||
template <typename... Args> using wformat_string = wstring_view;
 | 
			
		||||
inline auto runtime(wstring_view s) -> wstring_view { return s; }
 | 
			
		||||
#else
 | 
			
		||||
template <typename... Args>
 | 
			
		||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
 | 
			
		||||
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
 | 
			
		||||
  return {{s}};
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
template <> struct is_char<wchar_t> : std::true_type {};
 | 
			
		||||
template <> struct is_char<char16_t> : std::true_type {};
 | 
			
		||||
template <> struct is_char<char32_t> : std::true_type {};
 | 
			
		||||
 | 
			
		||||
#ifdef __cpp_char8_t
 | 
			
		||||
template <>
 | 
			
		||||
struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled()> {};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
template <typename... T>
 | 
			
		||||
constexpr auto make_wformat_args(T&... args)
 | 
			
		||||
    -> decltype(fmt::make_format_args<wformat_context>(args...)) {
 | 
			
		||||
  return fmt::make_format_args<wformat_context>(args...);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline namespace literals {
 | 
			
		||||
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
 | 
			
		||||
constexpr auto operator""_a(const wchar_t* s, size_t)
 | 
			
		||||
    -> detail::udl_arg<wchar_t> {
 | 
			
		||||
  return {s};
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
}  // namespace literals
 | 
			
		||||
 | 
			
		||||
template <typename It, typename Sentinel>
 | 
			
		||||
auto join(It begin, Sentinel end, wstring_view sep)
 | 
			
		||||
    -> join_view<It, Sentinel, wchar_t> {
 | 
			
		||||
  return {begin, end, sep};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Range>
 | 
			
		||||
auto join(Range&& range, wstring_view sep)
 | 
			
		||||
    -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
 | 
			
		||||
                 wchar_t> {
 | 
			
		||||
  return join(std::begin(range), std::end(range), sep);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
auto join(std::initializer_list<T> list, wstring_view sep)
 | 
			
		||||
    -> join_view<const T*, const T*, wchar_t> {
 | 
			
		||||
  return join(std::begin(list), std::end(list), sep);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T>
 | 
			
		||||
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep)
 | 
			
		||||
    -> tuple_join_view<wchar_t, T...> {
 | 
			
		||||
  return {tuple, sep};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
 | 
			
		||||
auto vformat(basic_string_view<Char> format_str,
 | 
			
		||||
             typename detail::vformat_args<Char>::type args)
 | 
			
		||||
    -> std::basic_string<Char> {
 | 
			
		||||
  auto buf = basic_memory_buffer<Char>();
 | 
			
		||||
  detail::vformat_to(buf, format_str, args);
 | 
			
		||||
  return to_string(buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T>
 | 
			
		||||
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
 | 
			
		||||
  return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename... T>
 | 
			
		||||
auto format_to(OutputIt out, wformat_string<T...> fmt, T&&... args)
 | 
			
		||||
    -> OutputIt {
 | 
			
		||||
  return vformat_to(out, fmt::wstring_view(fmt),
 | 
			
		||||
                    fmt::make_wformat_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Pass char_t as a default template parameter instead of using
 | 
			
		||||
// std::basic_string<char_t<S>> to reduce the symbol size.
 | 
			
		||||
template <typename S, typename... T,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
 | 
			
		||||
                        !std::is_same<Char, wchar_t>::value)>
 | 
			
		||||
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
 | 
			
		||||
  return vformat(detail::to_string_view(format_str),
 | 
			
		||||
                 fmt::make_format_args<buffered_context<Char>>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Locale, typename S,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
 | 
			
		||||
                            detail::is_exotic_char<Char>::value)>
 | 
			
		||||
inline auto vformat(const Locale& loc, const S& format_str,
 | 
			
		||||
                    typename detail::vformat_args<Char>::type args)
 | 
			
		||||
    -> std::basic_string<Char> {
 | 
			
		||||
  return detail::vformat(loc, detail::to_string_view(format_str), args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Locale, typename S, typename... T,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
 | 
			
		||||
                            detail::is_exotic_char<Char>::value)>
 | 
			
		||||
inline auto format(const Locale& loc, const S& format_str, T&&... args)
 | 
			
		||||
    -> std::basic_string<Char> {
 | 
			
		||||
  return detail::vformat(
 | 
			
		||||
      loc, detail::to_string_view(format_str),
 | 
			
		||||
      fmt::make_format_args<buffered_context<Char>>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename S,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
 | 
			
		||||
                            detail::is_exotic_char<Char>::value)>
 | 
			
		||||
auto vformat_to(OutputIt out, const S& format_str,
 | 
			
		||||
                typename detail::vformat_args<Char>::type args) -> OutputIt {
 | 
			
		||||
  auto&& buf = detail::get_buffer<Char>(out);
 | 
			
		||||
  detail::vformat_to(buf, detail::to_string_view(format_str), args);
 | 
			
		||||
  return detail::get_iterator(buf, out);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename S, typename... T,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value &&
 | 
			
		||||
                        !std::is_same<Char, char>::value &&
 | 
			
		||||
                        !std::is_same<Char, wchar_t>::value)>
 | 
			
		||||
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
 | 
			
		||||
  return vformat_to(out, detail::to_string_view(fmt),
 | 
			
		||||
                    fmt::make_format_args<buffered_context<Char>>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Locale, typename S, typename OutputIt, typename... Args,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
 | 
			
		||||
                            detail::is_locale<Locale>::value&&
 | 
			
		||||
                                detail::is_exotic_char<Char>::value)>
 | 
			
		||||
inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str,
 | 
			
		||||
                       typename detail::vformat_args<Char>::type args)
 | 
			
		||||
    -> OutputIt {
 | 
			
		||||
  auto&& buf = detail::get_buffer<Char>(out);
 | 
			
		||||
  vformat_to(buf, detail::to_string_view(format_str), args,
 | 
			
		||||
             detail::locale_ref(loc));
 | 
			
		||||
  return detail::get_iterator(buf, out);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename Locale, typename S, typename... T,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
 | 
			
		||||
                        detail::is_locale<Locale>::value &&
 | 
			
		||||
                        detail::is_exotic_char<Char>::value>
 | 
			
		||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
 | 
			
		||||
                      T&&... args) ->
 | 
			
		||||
    typename std::enable_if<enable, OutputIt>::type {
 | 
			
		||||
  return vformat_to(out, loc, detail::to_string_view(format_str),
 | 
			
		||||
                    fmt::make_format_args<buffered_context<Char>>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename Char, typename... Args,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
 | 
			
		||||
                            detail::is_exotic_char<Char>::value)>
 | 
			
		||||
inline auto vformat_to_n(OutputIt out, size_t n,
 | 
			
		||||
                         basic_string_view<Char> format_str,
 | 
			
		||||
                         typename detail::vformat_args<Char>::type args)
 | 
			
		||||
    -> format_to_n_result<OutputIt> {
 | 
			
		||||
  using traits = detail::fixed_buffer_traits;
 | 
			
		||||
  auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
 | 
			
		||||
  detail::vformat_to(buf, format_str, args);
 | 
			
		||||
  return {buf.out(), buf.count()};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename OutputIt, typename S, typename... T,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
 | 
			
		||||
                            detail::is_exotic_char<Char>::value)>
 | 
			
		||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
 | 
			
		||||
    -> format_to_n_result<OutputIt> {
 | 
			
		||||
  return vformat_to_n(out, n, fmt::basic_string_view<Char>(fmt),
 | 
			
		||||
                      fmt::make_format_args<buffered_context<Char>>(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename S, typename... T,
 | 
			
		||||
          typename Char = detail::format_string_char_t<S>,
 | 
			
		||||
          FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
 | 
			
		||||
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
 | 
			
		||||
  auto buf = detail::counting_buffer<Char>();
 | 
			
		||||
  detail::vformat_to(buf, detail::to_string_view(fmt),
 | 
			
		||||
                     fmt::make_format_args<buffered_context<Char>>(args...));
 | 
			
		||||
  return buf.count();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
 | 
			
		||||
  auto buf = wmemory_buffer();
 | 
			
		||||
  detail::vformat_to(buf, fmt, args);
 | 
			
		||||
  buf.push_back(L'\0');
 | 
			
		||||
  if (std::fputws(buf.data(), f) == -1)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void vprint(wstring_view fmt, wformat_args args) {
 | 
			
		||||
  vprint(stdout, fmt, args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T>
 | 
			
		||||
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
 | 
			
		||||
  return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
 | 
			
		||||
  return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T>
 | 
			
		||||
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
 | 
			
		||||
  return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
 | 
			
		||||
  return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
 | 
			
		||||
    -> std::wstring {
 | 
			
		||||
  auto buf = wmemory_buffer();
 | 
			
		||||
  detail::vformat_to(buf, ts, fmt, args);
 | 
			
		||||
  return fmt::to_string(buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T>
 | 
			
		||||
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
 | 
			
		||||
    -> std::wstring {
 | 
			
		||||
  return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T>
 | 
			
		||||
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
 | 
			
		||||
                          wformat_string<T...> fmt, const T&... args) {
 | 
			
		||||
  vprint(f, ts, fmt, fmt::make_wformat_args(args...));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename... T>
 | 
			
		||||
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
 | 
			
		||||
                          const T&... args) {
 | 
			
		||||
  return print(stdout, ts, fmt, args...);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Converts `value` to `std::wstring` using the default format for type `T`.
 | 
			
		||||
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
 | 
			
		||||
  return format(FMT_STRING(L"{}"), value);
 | 
			
		||||
}
 | 
			
		||||
FMT_END_EXPORT
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#endif  // FMT_XCHAR_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,135 @@
 | 
			
		|||
module;
 | 
			
		||||
 | 
			
		||||
// Put all implementation-provided headers into the global module fragment
 | 
			
		||||
// to prevent attachment to this module.
 | 
			
		||||
#ifndef FMT_IMPORT_STD
 | 
			
		||||
#  include <algorithm>
 | 
			
		||||
#  include <bitset>
 | 
			
		||||
#  include <chrono>
 | 
			
		||||
#  include <cmath>
 | 
			
		||||
#  include <complex>
 | 
			
		||||
#  include <cstddef>
 | 
			
		||||
#  include <cstdint>
 | 
			
		||||
#  include <cstdio>
 | 
			
		||||
#  include <cstdlib>
 | 
			
		||||
#  include <cstring>
 | 
			
		||||
#  include <ctime>
 | 
			
		||||
#  include <exception>
 | 
			
		||||
#  include <expected>
 | 
			
		||||
#  include <filesystem>
 | 
			
		||||
#  include <fstream>
 | 
			
		||||
#  include <functional>
 | 
			
		||||
#  include <iterator>
 | 
			
		||||
#  include <limits>
 | 
			
		||||
#  include <locale>
 | 
			
		||||
#  include <memory>
 | 
			
		||||
#  include <optional>
 | 
			
		||||
#  include <ostream>
 | 
			
		||||
#  include <source_location>
 | 
			
		||||
#  include <stdexcept>
 | 
			
		||||
#  include <string>
 | 
			
		||||
#  include <string_view>
 | 
			
		||||
#  include <system_error>
 | 
			
		||||
#  include <thread>
 | 
			
		||||
#  include <type_traits>
 | 
			
		||||
#  include <typeinfo>
 | 
			
		||||
#  include <utility>
 | 
			
		||||
#  include <variant>
 | 
			
		||||
#  include <vector>
 | 
			
		||||
#else
 | 
			
		||||
#  include <limits.h>
 | 
			
		||||
#  include <stdint.h>
 | 
			
		||||
#  include <stdio.h>
 | 
			
		||||
#  include <time.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
#include <climits>
 | 
			
		||||
#include <version>
 | 
			
		||||
 | 
			
		||||
#if __has_include(<cxxabi.h>)
 | 
			
		||||
#  include <cxxabi.h>
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(_MSC_VER) || defined(__MINGW32__)
 | 
			
		||||
#  include <intrin.h>
 | 
			
		||||
#endif
 | 
			
		||||
#if defined __APPLE__ || defined(__FreeBSD__)
 | 
			
		||||
#  include <xlocale.h>
 | 
			
		||||
#endif
 | 
			
		||||
#if __has_include(<winapifamily.h>)
 | 
			
		||||
#  include <winapifamily.h>
 | 
			
		||||
#endif
 | 
			
		||||
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
 | 
			
		||||
     defined(__linux__)) &&                            \
 | 
			
		||||
    (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
 | 
			
		||||
#  include <fcntl.h>
 | 
			
		||||
#  include <sys/stat.h>
 | 
			
		||||
#  include <sys/types.h>
 | 
			
		||||
#  ifndef _WIN32
 | 
			
		||||
#    include <unistd.h>
 | 
			
		||||
#  else
 | 
			
		||||
#    include <io.h>
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
#  if defined(__GLIBCXX__)
 | 
			
		||||
#    include <ext/stdio_filebuf.h>
 | 
			
		||||
#    include <ext/stdio_sync_filebuf.h>
 | 
			
		||||
#  endif
 | 
			
		||||
#  define WIN32_LEAN_AND_MEAN
 | 
			
		||||
#  include <windows.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
export module fmt;
 | 
			
		||||
 | 
			
		||||
#ifdef FMT_IMPORT_STD
 | 
			
		||||
import std;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define FMT_EXPORT export
 | 
			
		||||
#define FMT_BEGIN_EXPORT export {
 | 
			
		||||
#define FMT_END_EXPORT }
 | 
			
		||||
 | 
			
		||||
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
 | 
			
		||||
//  - all declarations are detached from module 'fmt'
 | 
			
		||||
//  - the module behaves like a traditional static library, too
 | 
			
		||||
//  - all library symbols are mangled traditionally
 | 
			
		||||
//  - you can mix TUs with either importing or #including the {fmt} API
 | 
			
		||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
 | 
			
		||||
extern "C++" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_OS
 | 
			
		||||
#  define FMT_OS 1
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// All library-provided declarations and definitions must be in the module
 | 
			
		||||
// purview to be exported.
 | 
			
		||||
#include "fmt/args.h"
 | 
			
		||||
#include "fmt/chrono.h"
 | 
			
		||||
#include "fmt/color.h"
 | 
			
		||||
#include "fmt/compile.h"
 | 
			
		||||
#include "fmt/format.h"
 | 
			
		||||
#if FMT_OS
 | 
			
		||||
#  include "fmt/os.h"
 | 
			
		||||
#endif
 | 
			
		||||
#include "fmt/ostream.h"
 | 
			
		||||
#include "fmt/printf.h"
 | 
			
		||||
#include "fmt/ranges.h"
 | 
			
		||||
#include "fmt/std.h"
 | 
			
		||||
#include "fmt/xchar.h"
 | 
			
		||||
 | 
			
		||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// gcc doesn't yet implement private module fragments
 | 
			
		||||
#if !FMT_GCC_VERSION
 | 
			
		||||
module :private;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if FMT_HAS_INCLUDE("format.cc")
 | 
			
		||||
#  include "format.cc"
 | 
			
		||||
#endif
 | 
			
		||||
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
 | 
			
		||||
#  include "os.cc"
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
// Formatting library for C++
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - 2016, Victor Zverovich
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
#include "fmt/format-inl.h"
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
template FMT_API auto dragonbox::to_decimal(float x) noexcept
 | 
			
		||||
    -> dragonbox::decimal_fp<float>;
 | 
			
		||||
template FMT_API auto dragonbox::to_decimal(double x) noexcept
 | 
			
		||||
    -> dragonbox::decimal_fp<double>;
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
 | 
			
		||||
template FMT_API locale_ref::locale_ref(const std::locale& loc);
 | 
			
		||||
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Explicit instantiations for char.
 | 
			
		||||
 | 
			
		||||
template FMT_API auto thousands_sep_impl(locale_ref)
 | 
			
		||||
    -> thousands_sep_result<char>;
 | 
			
		||||
template FMT_API auto decimal_point_impl(locale_ref) -> char;
 | 
			
		||||
 | 
			
		||||
template FMT_API void buffer<char>::append(const char*, const char*);
 | 
			
		||||
 | 
			
		||||
template FMT_API void vformat_to(buffer<char>&, string_view,
 | 
			
		||||
                                 typename vformat_args<>::type, locale_ref);
 | 
			
		||||
 | 
			
		||||
// Explicit instantiations for wchar_t.
 | 
			
		||||
 | 
			
		||||
template FMT_API auto thousands_sep_impl(locale_ref)
 | 
			
		||||
    -> thousands_sep_result<wchar_t>;
 | 
			
		||||
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
 | 
			
		||||
 | 
			
		||||
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,403 @@
 | 
			
		|||
// Formatting library for C++ - optional OS-specific functionality
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2012 - 2016, Victor Zverovich
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// For the license information refer to format.h.
 | 
			
		||||
 | 
			
		||||
// Disable bogus MSVC warnings.
 | 
			
		||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
 | 
			
		||||
#  define _CRT_SECURE_NO_WARNINGS
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "fmt/os.h"
 | 
			
		||||
 | 
			
		||||
#ifndef FMT_MODULE
 | 
			
		||||
#  include <climits>
 | 
			
		||||
 | 
			
		||||
#  if FMT_USE_FCNTL
 | 
			
		||||
#    include <sys/stat.h>
 | 
			
		||||
#    include <sys/types.h>
 | 
			
		||||
 | 
			
		||||
#    ifdef _WRS_KERNEL    // VxWorks7 kernel
 | 
			
		||||
#      include <ioLib.h>  // getpagesize
 | 
			
		||||
#    endif
 | 
			
		||||
 | 
			
		||||
#    ifndef _WIN32
 | 
			
		||||
#      include <unistd.h>
 | 
			
		||||
#    else
 | 
			
		||||
#      ifndef WIN32_LEAN_AND_MEAN
 | 
			
		||||
#        define WIN32_LEAN_AND_MEAN
 | 
			
		||||
#      endif
 | 
			
		||||
#      include <io.h>
 | 
			
		||||
#    endif  // _WIN32
 | 
			
		||||
#  endif    // FMT_USE_FCNTL
 | 
			
		||||
 | 
			
		||||
#  ifdef _WIN32
 | 
			
		||||
#    include <windows.h>
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
#  ifndef S_IRUSR
 | 
			
		||||
#    define S_IRUSR _S_IREAD
 | 
			
		||||
#  endif
 | 
			
		||||
#  ifndef S_IWUSR
 | 
			
		||||
#    define S_IWUSR _S_IWRITE
 | 
			
		||||
#  endif
 | 
			
		||||
#  ifndef S_IRGRP
 | 
			
		||||
#    define S_IRGRP 0
 | 
			
		||||
#  endif
 | 
			
		||||
#  ifndef S_IWGRP
 | 
			
		||||
#    define S_IWGRP 0
 | 
			
		||||
#  endif
 | 
			
		||||
#  ifndef S_IROTH
 | 
			
		||||
#    define S_IROTH 0
 | 
			
		||||
#  endif
 | 
			
		||||
#  ifndef S_IWOTH
 | 
			
		||||
#    define S_IWOTH 0
 | 
			
		||||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
// Return type of read and write functions.
 | 
			
		||||
using rwresult = int;
 | 
			
		||||
 | 
			
		||||
// On Windows the count argument to read and write is unsigned, so convert
 | 
			
		||||
// it from size_t preventing integer overflow.
 | 
			
		||||
inline unsigned convert_rwcount(std::size_t count) {
 | 
			
		||||
  return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
 | 
			
		||||
}
 | 
			
		||||
#elif FMT_USE_FCNTL
 | 
			
		||||
// Return type of read and write functions.
 | 
			
		||||
using rwresult = ssize_t;
 | 
			
		||||
 | 
			
		||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
 | 
			
		||||
#endif
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
FMT_BEGIN_NAMESPACE
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
class system_message {
 | 
			
		||||
  system_message(const system_message&) = delete;
 | 
			
		||||
  void operator=(const system_message&) = delete;
 | 
			
		||||
 | 
			
		||||
  unsigned long result_;
 | 
			
		||||
  wchar_t* message_;
 | 
			
		||||
 | 
			
		||||
  static bool is_whitespace(wchar_t c) noexcept {
 | 
			
		||||
    return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  explicit system_message(unsigned long error_code)
 | 
			
		||||
      : result_(0), message_(nullptr) {
 | 
			
		||||
    result_ = FormatMessageW(
 | 
			
		||||
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
 | 
			
		||||
            FORMAT_MESSAGE_IGNORE_INSERTS,
 | 
			
		||||
        nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
 | 
			
		||||
        reinterpret_cast<wchar_t*>(&message_), 0, nullptr);
 | 
			
		||||
    if (result_ != 0) {
 | 
			
		||||
      while (result_ != 0 && is_whitespace(message_[result_ - 1])) {
 | 
			
		||||
        --result_;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ~system_message() { LocalFree(message_); }
 | 
			
		||||
  explicit operator bool() const noexcept { return result_ != 0; }
 | 
			
		||||
  operator basic_string_view<wchar_t>() const noexcept {
 | 
			
		||||
    return basic_string_view<wchar_t>(message_, result_);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class utf8_system_category final : public std::error_category {
 | 
			
		||||
 public:
 | 
			
		||||
  const char* name() const noexcept override { return "system"; }
 | 
			
		||||
  std::string message(int error_code) const override {
 | 
			
		||||
    auto&& msg = system_message(error_code);
 | 
			
		||||
    if (msg) {
 | 
			
		||||
      auto utf8_message = to_utf8<wchar_t>();
 | 
			
		||||
      if (utf8_message.convert(msg)) {
 | 
			
		||||
        return utf8_message.str();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return "unknown error";
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
FMT_API const std::error_category& system_category() noexcept {
 | 
			
		||||
  static const detail::utf8_system_category category;
 | 
			
		||||
  return category;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::system_error vwindows_error(int err_code, string_view format_str,
 | 
			
		||||
                                 format_args args) {
 | 
			
		||||
  auto ec = std::error_code(err_code, system_category());
 | 
			
		||||
  return std::system_error(ec, vformat(format_str, args));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
 | 
			
		||||
                                  const char* message) noexcept {
 | 
			
		||||
  FMT_TRY {
 | 
			
		||||
    auto&& msg = system_message(error_code);
 | 
			
		||||
    if (msg) {
 | 
			
		||||
      auto utf8_message = to_utf8<wchar_t>();
 | 
			
		||||
      if (utf8_message.convert(msg)) {
 | 
			
		||||
        fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
 | 
			
		||||
                       string_view(utf8_message));
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  FMT_CATCH(...) {}
 | 
			
		||||
  format_error_code(out, error_code, message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void report_windows_error(int error_code, const char* message) noexcept {
 | 
			
		||||
  report_error(detail::format_windows_error, error_code, message);
 | 
			
		||||
}
 | 
			
		||||
#endif  // _WIN32
 | 
			
		||||
 | 
			
		||||
buffered_file::~buffered_file() noexcept {
 | 
			
		||||
  if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
 | 
			
		||||
    report_system_error(errno, "cannot close file");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
 | 
			
		||||
  FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
 | 
			
		||||
                nullptr);
 | 
			
		||||
  if (!file_)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
 | 
			
		||||
                           filename.c_str()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void buffered_file::close() {
 | 
			
		||||
  if (!file_) return;
 | 
			
		||||
  int result = FMT_SYSTEM(fclose(file_));
 | 
			
		||||
  file_ = nullptr;
 | 
			
		||||
  if (result != 0)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int buffered_file::descriptor() const {
 | 
			
		||||
#ifdef FMT_HAS_SYSTEM
 | 
			
		||||
  // fileno is a macro on OpenBSD.
 | 
			
		||||
#  ifdef fileno
 | 
			
		||||
#    undef fileno
 | 
			
		||||
#  endif
 | 
			
		||||
  int fd = FMT_POSIX_CALL(fileno(file_));
 | 
			
		||||
#elif defined(_WIN32)
 | 
			
		||||
  int fd = _fileno(file_);
 | 
			
		||||
#else
 | 
			
		||||
  int fd = fileno(file_);
 | 
			
		||||
#endif
 | 
			
		||||
  if (fd == -1)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
 | 
			
		||||
  return fd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if FMT_USE_FCNTL
 | 
			
		||||
#  ifdef _WIN32
 | 
			
		||||
using mode_t = int;
 | 
			
		||||
#  endif
 | 
			
		||||
 | 
			
		||||
constexpr mode_t default_open_mode =
 | 
			
		||||
    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
 | 
			
		||||
 | 
			
		||||
file::file(cstring_view path, int oflag) {
 | 
			
		||||
#  if defined(_WIN32) && !defined(__MINGW32__)
 | 
			
		||||
  fd_ = -1;
 | 
			
		||||
  auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
 | 
			
		||||
  *this = file::open_windows_file(converted.c_str(), oflag);
 | 
			
		||||
#  else
 | 
			
		||||
  FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
 | 
			
		||||
  if (fd_ == -1)
 | 
			
		||||
    FMT_THROW(
 | 
			
		||||
        system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
 | 
			
		||||
#  endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
file::~file() noexcept {
 | 
			
		||||
  // Don't retry close in case of EINTR!
 | 
			
		||||
  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
 | 
			
		||||
  if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
 | 
			
		||||
    report_system_error(errno, "cannot close file");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void file::close() {
 | 
			
		||||
  if (fd_ == -1) return;
 | 
			
		||||
  // Don't retry close in case of EINTR!
 | 
			
		||||
  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
 | 
			
		||||
  int result = FMT_POSIX_CALL(close(fd_));
 | 
			
		||||
  fd_ = -1;
 | 
			
		||||
  if (result != 0)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
long long file::size() const {
 | 
			
		||||
#  ifdef _WIN32
 | 
			
		||||
  // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
 | 
			
		||||
  // is less than 0x0500 as is the case with some default MinGW builds.
 | 
			
		||||
  // Both functions support large file sizes.
 | 
			
		||||
  DWORD size_upper = 0;
 | 
			
		||||
  HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
 | 
			
		||||
  DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
 | 
			
		||||
  if (size_lower == INVALID_FILE_SIZE) {
 | 
			
		||||
    DWORD error = GetLastError();
 | 
			
		||||
    if (error != NO_ERROR)
 | 
			
		||||
      FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
 | 
			
		||||
  }
 | 
			
		||||
  unsigned long long long_size = size_upper;
 | 
			
		||||
  return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
 | 
			
		||||
#  else
 | 
			
		||||
  using Stat = struct stat;
 | 
			
		||||
  Stat file_stat = Stat();
 | 
			
		||||
  if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes")));
 | 
			
		||||
  static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
 | 
			
		||||
                "return type of file::size is not large enough");
 | 
			
		||||
  return file_stat.st_size;
 | 
			
		||||
#  endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t file::read(void* buffer, std::size_t count) {
 | 
			
		||||
  rwresult result = 0;
 | 
			
		||||
  FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
 | 
			
		||||
  if (result < 0)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
 | 
			
		||||
  return detail::to_unsigned(result);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t file::write(const void* buffer, std::size_t count) {
 | 
			
		||||
  rwresult result = 0;
 | 
			
		||||
  FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
 | 
			
		||||
  if (result < 0)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
 | 
			
		||||
  return detail::to_unsigned(result);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
file file::dup(int fd) {
 | 
			
		||||
  // Don't retry as dup doesn't return EINTR.
 | 
			
		||||
  // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
 | 
			
		||||
  int new_fd = FMT_POSIX_CALL(dup(fd));
 | 
			
		||||
  if (new_fd == -1)
 | 
			
		||||
    FMT_THROW(system_error(
 | 
			
		||||
        errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
 | 
			
		||||
  return file(new_fd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void file::dup2(int fd) {
 | 
			
		||||
  int result = 0;
 | 
			
		||||
  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
 | 
			
		||||
  if (result == -1) {
 | 
			
		||||
    FMT_THROW(system_error(
 | 
			
		||||
        errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
 | 
			
		||||
        fd));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void file::dup2(int fd, std::error_code& ec) noexcept {
 | 
			
		||||
  int result = 0;
 | 
			
		||||
  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
 | 
			
		||||
  if (result == -1) ec = std::error_code(errno, std::generic_category());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
buffered_file file::fdopen(const char* mode) {
 | 
			
		||||
// Don't retry as fdopen doesn't return EINTR.
 | 
			
		||||
#  if defined(__MINGW32__) && defined(_POSIX_)
 | 
			
		||||
  FILE* f = ::fdopen(fd_, mode);
 | 
			
		||||
#  else
 | 
			
		||||
  FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
 | 
			
		||||
#  endif
 | 
			
		||||
  if (!f) {
 | 
			
		||||
    FMT_THROW(system_error(
 | 
			
		||||
        errno, FMT_STRING("cannot associate stream with file descriptor")));
 | 
			
		||||
  }
 | 
			
		||||
  buffered_file bf(f);
 | 
			
		||||
  fd_ = -1;
 | 
			
		||||
  return bf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#  if defined(_WIN32) && !defined(__MINGW32__)
 | 
			
		||||
file file::open_windows_file(wcstring_view path, int oflag) {
 | 
			
		||||
  int fd = -1;
 | 
			
		||||
  auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
 | 
			
		||||
  if (fd == -1) {
 | 
			
		||||
    FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"),
 | 
			
		||||
                           detail::to_utf8<wchar_t>(path.c_str()).c_str()));
 | 
			
		||||
  }
 | 
			
		||||
  return file(fd);
 | 
			
		||||
}
 | 
			
		||||
#  endif
 | 
			
		||||
 | 
			
		||||
pipe::pipe() {
 | 
			
		||||
  int fds[2] = {};
 | 
			
		||||
#  ifdef _WIN32
 | 
			
		||||
  // Make the default pipe capacity same as on Linux 2.6.11+.
 | 
			
		||||
  enum { DEFAULT_CAPACITY = 65536 };
 | 
			
		||||
  int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
 | 
			
		||||
#  else
 | 
			
		||||
  // Don't retry as the pipe function doesn't return EINTR.
 | 
			
		||||
  // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
 | 
			
		||||
  int result = FMT_POSIX_CALL(pipe(fds));
 | 
			
		||||
#  endif
 | 
			
		||||
  if (result != 0)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
 | 
			
		||||
  // The following assignments don't throw.
 | 
			
		||||
  read_end = file(fds[0]);
 | 
			
		||||
  write_end = file(fds[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#  if !defined(__MSDOS__)
 | 
			
		||||
long getpagesize() {
 | 
			
		||||
#    ifdef _WIN32
 | 
			
		||||
  SYSTEM_INFO si;
 | 
			
		||||
  GetSystemInfo(&si);
 | 
			
		||||
  return si.dwPageSize;
 | 
			
		||||
#    else
 | 
			
		||||
#      ifdef _WRS_KERNEL
 | 
			
		||||
  long size = FMT_POSIX_CALL(getpagesize());
 | 
			
		||||
#      else
 | 
			
		||||
  long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
 | 
			
		||||
#      endif
 | 
			
		||||
 | 
			
		||||
  if (size < 0)
 | 
			
		||||
    FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
 | 
			
		||||
  return size;
 | 
			
		||||
#    endif
 | 
			
		||||
}
 | 
			
		||||
#  endif
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
void file_buffer::grow(buffer<char>& buf, size_t) {
 | 
			
		||||
  if (buf.size() == buf.capacity()) static_cast<file_buffer&>(buf).flush();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
file_buffer::file_buffer(cstring_view path, const ostream_params& params)
 | 
			
		||||
    : buffer<char>(grow), file_(path, params.oflag) {
 | 
			
		||||
  set(new char[params.buffer_size], params.buffer_size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
file_buffer::file_buffer(file_buffer&& other) noexcept
 | 
			
		||||
    : buffer<char>(grow, other.data(), other.size(), other.capacity()),
 | 
			
		||||
      file_(std::move(other.file_)) {
 | 
			
		||||
  other.clear();
 | 
			
		||||
  other.set(nullptr, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
file_buffer::~file_buffer() {
 | 
			
		||||
  flush();
 | 
			
		||||
  delete[] data();
 | 
			
		||||
}
 | 
			
		||||
}  // namespace detail
 | 
			
		||||
 | 
			
		||||
ostream::~ostream() = default;
 | 
			
		||||
#endif  // FMT_USE_FCNTL
 | 
			
		||||
FMT_END_NAMESPACE
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +15,10 @@ JavaVM* g_javaVM = nullptr;
 | 
			
		|||
jclass g_gakumasHookMainClass = nullptr;
 | 
			
		||||
jmethodID showToastMethodId = nullptr;
 | 
			
		||||
 | 
			
		||||
bool UnityResolveProgress::startInit = false;
 | 
			
		||||
UnityResolveProgress::Progress UnityResolveProgress::assembliesProgress{};
 | 
			
		||||
UnityResolveProgress::Progress UnityResolveProgress::classProgress{};
 | 
			
		||||
 | 
			
		||||
namespace
 | 
			
		||||
{
 | 
			
		||||
    class AndroidHookInstaller : public GakumasLocal::HookInstaller
 | 
			
		||||
| 
						 | 
				
			
			@ -112,3 +116,39 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env,
 | 
			
		|||
    const std::string configJson = configJsonStrChars;
 | 
			
		||||
    GakumasLocal::Config::LoadConfig(configJson);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C"
 | 
			
		||||
JNIEXPORT jint JNICALL
 | 
			
		||||
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_pluginCallbackLooper(JNIEnv *env,
 | 
			
		||||
                                                                             jclass clazz) {
 | 
			
		||||
    GakumasLocal::Log::ToastLoop(env, clazz);
 | 
			
		||||
 | 
			
		||||
    if (UnityResolveProgress::startInit) {
 | 
			
		||||
        return 9;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
extern "C"
 | 
			
		||||
JNIEXPORT void JNICALL
 | 
			
		||||
Java_io_github_chinosk_gakumas_localify_models_NativeInitProgress_pluginInitProgressLooper(
 | 
			
		||||
        JNIEnv *env, jclass clazz, jobject progress) {
 | 
			
		||||
 | 
			
		||||
    // jclass progressClass = env->GetObjectClass(progress);
 | 
			
		||||
 | 
			
		||||
    static jfieldID startInitFieldID = env->GetStaticFieldID(clazz, "startInit", "Z");
 | 
			
		||||
 | 
			
		||||
    static jmethodID setAssembliesProgressDataMethodID = env->GetMethodID(clazz, "setAssembliesProgressData", "(JJ)V");
 | 
			
		||||
    static jmethodID setClassProgressDataMethodID = env->GetMethodID(clazz, "setClassProgressData", "(JJ)V");
 | 
			
		||||
 | 
			
		||||
    // jboolean startInit = env->GetStaticBooleanField(clazz, startInitFieldID);
 | 
			
		||||
 | 
			
		||||
    env->SetStaticBooleanField(clazz, startInitFieldID, UnityResolveProgress::startInit);
 | 
			
		||||
 | 
			
		||||
    env->CallVoidMethod(progress, setAssembliesProgressDataMethodID,
 | 
			
		||||
                        UnityResolveProgress::assembliesProgress.current, UnityResolveProgress::assembliesProgress.total);
 | 
			
		||||
    env->CallVoidMethod(progress, setClassProgressDataMethodID,
 | 
			
		||||
                        UnityResolveProgress::classProgress.current, UnityResolveProgress::classProgress.total);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
#include "shadowhook.h"
 | 
			
		||||
#include <android/log.h>
 | 
			
		||||
 | 
			
		||||
#define ADD_HOOK(name, addr)                                                                       \
 | 
			
		||||
	name##_Addr = reinterpret_cast<name##_Type>(addr);                                             \
 | 
			
		||||
	if (addr) {                                                                                    \
 | 
			
		||||
    	auto stub = hookInstaller->InstallHook(reinterpret_cast<void*>(addr),                      \
 | 
			
		||||
                                               reinterpret_cast<void*>(name##_Hook),               \
 | 
			
		||||
                                               reinterpret_cast<void**>(&name##_Orig));            \
 | 
			
		||||
        if (stub == NULL) {                                                                        \
 | 
			
		||||
            int error_num = shadowhook_get_errno();                                                \
 | 
			
		||||
            const char *error_msg = shadowhook_to_errmsg(error_num);                               \
 | 
			
		||||
            Log::ErrorFmt("ADD_HOOK: %s at %p failed: %s", #name, addr, error_msg);                \
 | 
			
		||||
        }                                                                                          \
 | 
			
		||||
        else {                                                                                     \
 | 
			
		||||
            hookedStubs.emplace(stub);                                                             \
 | 
			
		||||
            GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr);                         \
 | 
			
		||||
        }                                                                                          \
 | 
			
		||||
    }                                                                                              \
 | 
			
		||||
    else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr);                      \
 | 
			
		||||
    if (Config::lazyInit) UnityResolveProgress::classProgress.current++
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,145 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.core.content.FileProvider
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.json
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfigSerializer
 | 
			
		||||
import kotlinx.serialization.SerializationException
 | 
			
		||||
import java.io.File
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface IHasConfigItems {
 | 
			
		||||
    var config: GakumasConfig
 | 
			
		||||
    var programConfig: ProgramConfig
 | 
			
		||||
 | 
			
		||||
    fun saveConfig() {}  // do nothing
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IConfigurableActivity<T : Activity> : IHasConfigItems
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fun <T> T.getConfigContent(): String where T : Activity {
 | 
			
		||||
    val configFile = File(filesDir, "gkms-config.json")
 | 
			
		||||
    return if (configFile.exists()) {
 | 
			
		||||
        configFile.readText()
 | 
			
		||||
    } else {
 | 
			
		||||
        Toast.makeText(this, "检测到第一次启动,初始化配置文件...", Toast.LENGTH_SHORT).show()
 | 
			
		||||
        configFile.writeText("{}")
 | 
			
		||||
        "{}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun <T> T.getProgramConfigContent(
 | 
			
		||||
    excludes: List<String> = emptyList(),
 | 
			
		||||
    origProgramConfig: ProgramConfig? = null
 | 
			
		||||
): String where T : Activity {
 | 
			
		||||
    val configFile = File(filesDir, "localify-config.json")
 | 
			
		||||
    if (excludes.isEmpty()) {
 | 
			
		||||
        return if (configFile.exists()) {
 | 
			
		||||
            configFile.readText()
 | 
			
		||||
        } else {
 | 
			
		||||
            "{}"
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        return if (origProgramConfig == null) {
 | 
			
		||||
            if (configFile.exists()) {
 | 
			
		||||
                val parsedConfig = json.decodeFromString<ProgramConfig>(configFile.readText())
 | 
			
		||||
                json.encodeToString(ProgramConfigSerializer(excludes), parsedConfig)
 | 
			
		||||
            } else {
 | 
			
		||||
                "{}"
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            json.encodeToString(ProgramConfigSerializer(excludes), origProgramConfig)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun <T> T.loadConfig() where T : Activity, T : IHasConfigItems {
 | 
			
		||||
    val configStr = getConfigContent()
 | 
			
		||||
    config = try {
 | 
			
		||||
        json.decodeFromString<GakumasConfig>(configStr)
 | 
			
		||||
    } catch (e: SerializationException) {
 | 
			
		||||
        Toast.makeText(this, "配置文件异常: $e", Toast.LENGTH_SHORT).show()
 | 
			
		||||
        GakumasConfig()
 | 
			
		||||
    }
 | 
			
		||||
    saveConfig()
 | 
			
		||||
 | 
			
		||||
    val programConfigStr = getProgramConfigContent()
 | 
			
		||||
    programConfig = try {
 | 
			
		||||
        json.decodeFromString<ProgramConfig>(programConfigStr)
 | 
			
		||||
    } catch (e: SerializationException) {
 | 
			
		||||
        ProgramConfig()
 | 
			
		||||
    }
 | 
			
		||||
    if (programConfig.useAPIAssetsURL.isEmpty()) {
 | 
			
		||||
        programConfig.useAPIAssetsURL = getString(R.string.default_assets_check_api)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
 | 
			
		||||
    val lastStartPluginVersionFile = File(filesDir, "lastStartPluginVersion.txt")
 | 
			
		||||
    val lastStartPluginVersion = if (lastStartPluginVersionFile.exists()) {
 | 
			
		||||
        lastStartPluginVersionFile.readText()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        "null"
 | 
			
		||||
    }
 | 
			
		||||
    val packInfo = packageManager.getPackageInfo(packageName, 0)
 | 
			
		||||
    val version = packInfo.versionName
 | 
			
		||||
    val versionCode = packInfo.longVersionCode
 | 
			
		||||
    val currentPluginVersion = "$version ($versionCode)"
 | 
			
		||||
    if (lastStartPluginVersion != currentPluginVersion) {  // 插件版本更新,强制启用资源更新检查
 | 
			
		||||
        lastStartPluginVersionFile.writeText(currentPluginVersion)
 | 
			
		||||
        programConfig.checkBuiltInAssets = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val intent = Intent().apply {
 | 
			
		||||
        setClassName(
 | 
			
		||||
            "com.bandainamcoent.idolmaster_gakuen",
 | 
			
		||||
            "com.google.firebase.MessagingUnityPlayerActivity"
 | 
			
		||||
        )
 | 
			
		||||
        putExtra("gkmsData", getConfigContent())
 | 
			
		||||
        putExtra(
 | 
			
		||||
            "localData",
 | 
			
		||||
            getProgramConfigContent(listOf("transRemoteZipUrl", "useAPIAssetsURL",
 | 
			
		||||
                "localAPIAssetsVersion", "p"), programConfig)
 | 
			
		||||
        )
 | 
			
		||||
        putExtra("lVerName", version)
 | 
			
		||||
        flags = Intent.FLAG_ACTIVITY_NEW_TASK
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val updateFile = File(filesDir, "update_trans.zip")
 | 
			
		||||
    val updateAPIFile = File(filesDir, "remote_files/remote.zip")
 | 
			
		||||
    val targetFile = if (programConfig.useAPIAssets && updateAPIFile.exists()) {
 | 
			
		||||
        updateAPIFile
 | 
			
		||||
    }
 | 
			
		||||
    else if (programConfig.useRemoteAssets && updateFile.exists()) {
 | 
			
		||||
        updateFile
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (targetFile != null) {
 | 
			
		||||
        val dirUri = FileProvider.getUriForFile(
 | 
			
		||||
            this,
 | 
			
		||||
            "io.github.chinosk.gakumas.localify.fileprovider",
 | 
			
		||||
            File(targetFile.absolutePath)
 | 
			
		||||
        )
 | 
			
		||||
        // intent.setDataAndType(dirUri, "resource/file")
 | 
			
		||||
 | 
			
		||||
        grantUriPermission(
 | 
			
		||||
            "com.bandainamcoent.idolmaster_gakuen",
 | 
			
		||||
            dirUri,
 | 
			
		||||
            Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
 | 
			
		||||
        )
 | 
			
		||||
        intent.putExtra("resource_file", dirUri)
 | 
			
		||||
        // intent.clipData = ClipData.newRawUri("resource_file", dirUri)
 | 
			
		||||
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    startActivity(intent)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +1,31 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.lifecycle.ViewModelProvider
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModel
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModelFactory
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.asStateFlow
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface ConfigListener {
 | 
			
		||||
    fun onClickStartGame()
 | 
			
		||||
    fun onEnabledChanged(value: Boolean)
 | 
			
		||||
    fun onForceExportResourceChanged(value: Boolean)
 | 
			
		||||
    fun onLoginAsIOSChanged(value: Boolean)
 | 
			
		||||
    fun onTextTestChanged(value: Boolean)
 | 
			
		||||
    fun onUseMasterTransChanged(value: Boolean)
 | 
			
		||||
    fun onReplaceFontChanged(value: Boolean)
 | 
			
		||||
    fun onLazyInitChanged(value: Boolean)
 | 
			
		||||
    fun onEnableFreeCameraChanged(value: Boolean)
 | 
			
		||||
    fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int)
 | 
			
		||||
    fun onUnlockAllLiveChanged(value: Boolean)
 | 
			
		||||
    fun onUnlockAllLiveCostumeChanged(value: Boolean)
 | 
			
		||||
    fun onLiveCustomeDressChanged(value: Boolean)
 | 
			
		||||
    fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int)
 | 
			
		||||
    fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int)
 | 
			
		||||
| 
						 | 
				
			
			@ -46,53 +59,108 @@ interface ConfigListener {
 | 
			
		|||
    fun onBUseArmCorrectionChanged(value: Boolean)
 | 
			
		||||
    fun onBUseScaleChanged(value: Boolean)
 | 
			
		||||
    fun onBClickPresetChanged(index: Int)
 | 
			
		||||
    fun onPCheckBuiltInAssetsChanged(value: Boolean)
 | 
			
		||||
    fun onPUseRemoteAssetsChanged(value: Boolean)
 | 
			
		||||
    fun onPCleanLocalAssetsChanged(value: Boolean)
 | 
			
		||||
    fun onPDelRemoteAfterUpdateChanged(value: Boolean)
 | 
			
		||||
    fun onPTransRemoteZipUrlChanged(s: CharSequence, start: Int, before: Int, count: Int)
 | 
			
		||||
    fun mainPageAssetsViewDataUpdate(downloadAbleState: Boolean? = null,
 | 
			
		||||
                                     downloadProgressState: Float? = null,
 | 
			
		||||
                                     localResourceVersionState: String? = null,
 | 
			
		||||
                                     errorString: String? = null,
 | 
			
		||||
                                     localAPIResourceVersion: String? = null)
 | 
			
		||||
    fun onPUseAPIAssetsChanged(value: Boolean)
 | 
			
		||||
    fun onPUseAPIAssetsURLChanged(s: CharSequence, start: Int, before: Int, count: Int)
 | 
			
		||||
    fun mainUIConfirmStatUpdate(isShow: Boolean? = null, title: String? = null,
 | 
			
		||||
                                content: String? = null,
 | 
			
		||||
                                onConfirm: (() -> Unit)? = { mainUIConfirmStatUpdate(isShow = false) },
 | 
			
		||||
                                onCancel: (() -> Unit)? = { mainUIConfirmStatUpdate(isShow = false) })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class UserConfigViewModelFactory(private val initialValue: GakumasConfig) : ViewModelProvider.Factory {
 | 
			
		||||
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
 | 
			
		||||
        if (modelClass.isAssignableFrom(UserConfigViewModel::class.java)) {
 | 
			
		||||
            @Suppress("UNCHECKED_CAST")
 | 
			
		||||
            return UserConfigViewModel(initialValue) as T
 | 
			
		||||
        }
 | 
			
		||||
        throw IllegalArgumentException("Unknown ViewModel class")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class UserConfigViewModel(initValue: GakumasConfig) : ViewModel() {
 | 
			
		||||
    val configState = MutableStateFlow(initValue)
 | 
			
		||||
    val config: StateFlow<GakumasConfig> = configState.asStateFlow()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface ConfigUpdateListener: ConfigListener {
 | 
			
		||||
    var binding: ActivityMainBinding
 | 
			
		||||
interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		||||
    var factory: UserConfigViewModelFactory
 | 
			
		||||
    var viewModel: UserConfigViewModel
 | 
			
		||||
 | 
			
		||||
    var programConfigFactory: ProgramConfigViewModelFactory
 | 
			
		||||
    var programConfigViewModel: ProgramConfigViewModel
 | 
			
		||||
 | 
			
		||||
    fun pushKeyEvent(event: KeyEvent): Boolean
 | 
			
		||||
    fun getConfigContent(): String
 | 
			
		||||
    fun checkConfigAndUpdateView()
 | 
			
		||||
    fun saveConfig()
 | 
			
		||||
    fun checkConfigAndUpdateView() {}  // do nothing
 | 
			
		||||
    // fun saveConfig()
 | 
			
		||||
    fun saveProgramConfig()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun onEnabledChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.enabled = value
 | 
			
		||||
        config.enabled = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        pushKeyEvent(KeyEvent(1145, 29))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onForceExportResourceChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.forceExportResource = value
 | 
			
		||||
        config.forceExportResource = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        pushKeyEvent(KeyEvent(1145, 30))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLoginAsIOSChanged(value: Boolean) {
 | 
			
		||||
        config.loginAsIOS = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onReplaceFontChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.replaceFont = value
 | 
			
		||||
        config.replaceFont = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        pushKeyEvent(KeyEvent(1145, 30))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLazyInitChanged(value: Boolean) {
 | 
			
		||||
        config.lazyInit = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onTextTestChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.textTest = value
 | 
			
		||||
        config.textTest = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUseMasterTransChanged(value: Boolean) {
 | 
			
		||||
        config.useMasterTrans = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDumpTextChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.dumpText = value
 | 
			
		||||
        config.dumpText = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onEnableFreeCameraChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.enableFreeCamera = value
 | 
			
		||||
        config.enableFreeCamera = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUnlockAllLiveChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.unlockAllLive = value
 | 
			
		||||
        config.unlockAllLive = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUnlockAllLiveCostumeChanged(value: Boolean) {
 | 
			
		||||
        config.unlockAllLiveCostume = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +173,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
            } else {
 | 
			
		||||
                valueStr.toInt()
 | 
			
		||||
            }
 | 
			
		||||
            binding.config!!.targetFrameRate = value
 | 
			
		||||
            config.targetFrameRate = value
 | 
			
		||||
            saveConfig()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -114,22 +182,22 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLiveCustomeDressChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.enableLiveCustomeDress = value
 | 
			
		||||
        config.enableLiveCustomeDress = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.liveCustomeCostumeId = s.toString()
 | 
			
		||||
        config.liveCustomeCostumeId = s.toString()
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUseCustomeGraphicSettingsChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.useCustomeGraphicSettings = value
 | 
			
		||||
        config.useCustomeGraphicSettings = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onRenderScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.renderScale = try {
 | 
			
		||||
        config.renderScale = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +207,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onQualitySettingsLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.qualitySettingsLevel = try {
 | 
			
		||||
        config.qualitySettingsLevel = try {
 | 
			
		||||
            s.toString().toInt()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +217,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onVolumeIndexChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.volumeIndex = try {
 | 
			
		||||
        config.volumeIndex = try {
 | 
			
		||||
            s.toString().toInt()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +227,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onMaxBufferPixelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.maxBufferPixel = try {
 | 
			
		||||
        config.maxBufferPixel = try {
 | 
			
		||||
            s.toString().toInt()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -169,12 +237,12 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.liveCustomeHeadId = s.toString()
 | 
			
		||||
        config.liveCustomeHeadId = s.toString()
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onReflectionQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.reflectionQualityLevel = try {
 | 
			
		||||
        config.reflectionQualityLevel = try {
 | 
			
		||||
            val value = s.toString().toInt()
 | 
			
		||||
            if (value > 5) 5 else value
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -185,7 +253,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLodQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.lodQualityLevel = try {
 | 
			
		||||
        config.lodQualityLevel = try {
 | 
			
		||||
            val value = s.toString().toInt()
 | 
			
		||||
            if (value > 5) 5 else value
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -198,44 +266,44 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    override fun onChangePresetQuality(level: Int) {
 | 
			
		||||
        when (level) {
 | 
			
		||||
            0 -> {
 | 
			
		||||
                binding.config!!.renderScale = 0.5f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 1
 | 
			
		||||
                binding.config!!.volumeIndex = 0
 | 
			
		||||
                binding.config!!.maxBufferPixel = 1024
 | 
			
		||||
                binding.config!!.lodQualityLevel = 1
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 1
 | 
			
		||||
                config.renderScale = 0.5f
 | 
			
		||||
                config.qualitySettingsLevel = 1
 | 
			
		||||
                config.volumeIndex = 0
 | 
			
		||||
                config.maxBufferPixel = 1024
 | 
			
		||||
                config.lodQualityLevel = 1
 | 
			
		||||
                config.reflectionQualityLevel = 1
 | 
			
		||||
            }
 | 
			
		||||
            1 -> {
 | 
			
		||||
                binding.config!!.renderScale = 0.59f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 1
 | 
			
		||||
                binding.config!!.volumeIndex = 1
 | 
			
		||||
                binding.config!!.maxBufferPixel = 1440
 | 
			
		||||
                binding.config!!.lodQualityLevel = 2
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 2
 | 
			
		||||
                config.renderScale = 0.59f
 | 
			
		||||
                config.qualitySettingsLevel = 1
 | 
			
		||||
                config.volumeIndex = 1
 | 
			
		||||
                config.maxBufferPixel = 1440
 | 
			
		||||
                config.lodQualityLevel = 2
 | 
			
		||||
                config.reflectionQualityLevel = 2
 | 
			
		||||
            }
 | 
			
		||||
            2 -> {
 | 
			
		||||
                binding.config!!.renderScale = 0.67f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 2
 | 
			
		||||
                binding.config!!.volumeIndex = 2
 | 
			
		||||
                binding.config!!.maxBufferPixel = 2538
 | 
			
		||||
                binding.config!!.lodQualityLevel = 3
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 3
 | 
			
		||||
                config.renderScale = 0.67f
 | 
			
		||||
                config.qualitySettingsLevel = 2
 | 
			
		||||
                config.volumeIndex = 2
 | 
			
		||||
                config.maxBufferPixel = 2538
 | 
			
		||||
                config.lodQualityLevel = 3
 | 
			
		||||
                config.reflectionQualityLevel = 3
 | 
			
		||||
            }
 | 
			
		||||
            3 -> {
 | 
			
		||||
                binding.config!!.renderScale = 0.77f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 3
 | 
			
		||||
                binding.config!!.volumeIndex = 3
 | 
			
		||||
                binding.config!!.maxBufferPixel = 3384
 | 
			
		||||
                binding.config!!.lodQualityLevel = 4
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 4
 | 
			
		||||
                config.renderScale = 0.77f
 | 
			
		||||
                config.qualitySettingsLevel = 3
 | 
			
		||||
                config.volumeIndex = 3
 | 
			
		||||
                config.maxBufferPixel = 3384
 | 
			
		||||
                config.lodQualityLevel = 4
 | 
			
		||||
                config.reflectionQualityLevel = 4
 | 
			
		||||
            }
 | 
			
		||||
            4 -> {
 | 
			
		||||
                binding.config!!.renderScale = 1.0f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 5
 | 
			
		||||
                binding.config!!.volumeIndex = 4
 | 
			
		||||
                binding.config!!.maxBufferPixel = 8190
 | 
			
		||||
                binding.config!!.lodQualityLevel = 5
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 5
 | 
			
		||||
                config.renderScale = 1.0f
 | 
			
		||||
                config.qualitySettingsLevel = 5
 | 
			
		||||
                config.volumeIndex = 4
 | 
			
		||||
                config.maxBufferPixel = 8190
 | 
			
		||||
                config.lodQualityLevel = 5
 | 
			
		||||
                config.reflectionQualityLevel = 5
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
| 
						 | 
				
			
			@ -243,33 +311,31 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onGameOrientationChanged(checkedId: Int) {
 | 
			
		||||
        when (checkedId) {
 | 
			
		||||
            R.id.radioButtonGameDefault -> binding.config!!.gameOrientation = 0
 | 
			
		||||
            R.id.radioButtonGamePortrait -> binding.config!!.gameOrientation = 1
 | 
			
		||||
            R.id.radioButtonGameLandscape -> binding.config!!.gameOrientation = 2
 | 
			
		||||
        if (checkedId in listOf(0, 1, 2)) {
 | 
			
		||||
            config.gameOrientation = checkedId
 | 
			
		||||
        }
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onEnableBreastParamChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.enableBreastParam = value
 | 
			
		||||
        config.enableBreastParam = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBUseArmCorrectionChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.bUseArmCorrection = value
 | 
			
		||||
        config.bUseArmCorrection = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBUseScaleChanged(value: Boolean) {
 | 
			
		||||
        binding.config!!.bUseScale = value
 | 
			
		||||
        config.bUseScale = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBDampingChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.bDamping = try {
 | 
			
		||||
        config.bDamping = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -279,7 +345,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBStiffnessChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        binding.config!!.bStiffness = try {
 | 
			
		||||
        config.bStiffness = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -289,7 +355,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBSpringChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        binding.config!!.bSpring = try {
 | 
			
		||||
        config.bSpring = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -299,7 +365,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBPendulumChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        binding.config!!.bPendulum = try {
 | 
			
		||||
        config.bPendulum = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -309,7 +375,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBPendulumRangeChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        binding.config!!.bPendulumRange = try {
 | 
			
		||||
        config.bPendulumRange = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -319,7 +385,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBAverageChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        binding.config!!.bAverage = try {
 | 
			
		||||
        config.bAverage = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -329,7 +395,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBRootWeightChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        binding.config!!.bRootWeight = try {
 | 
			
		||||
        config.bRootWeight = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -339,13 +405,13 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBUseLimitChanged(value: Boolean){
 | 
			
		||||
        binding.config!!.bUseLimit = value
 | 
			
		||||
        config.bUseLimit = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitXxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.bLimitXx = try {
 | 
			
		||||
        config.bLimitXx = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -355,7 +421,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitXyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.bLimitXy = try {
 | 
			
		||||
        config.bLimitXy = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -365,7 +431,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitYxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.bLimitYx = try {
 | 
			
		||||
        config.bLimitYx = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -375,7 +441,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitYyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.bLimitYy = try {
 | 
			
		||||
        config.bLimitYy = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -385,7 +451,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitZxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.bLimitZx = try {
 | 
			
		||||
        config.bLimitZx = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -395,7 +461,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitZyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.bLimitZy = try {
 | 
			
		||||
        config.bLimitZy = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -406,7 +472,7 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    override fun onBScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        binding.config!!.bScale = try {
 | 
			
		||||
        config.bScale = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -436,30 +502,124 @@ interface ConfigUpdateListener: ConfigListener {
 | 
			
		|||
                1f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        binding.config!!.bDamping = setData[0]
 | 
			
		||||
        binding.config!!.bStiffness = setData[1]
 | 
			
		||||
        binding.config!!.bSpring = setData[2]
 | 
			
		||||
        binding.config!!.bPendulum = setData[3]
 | 
			
		||||
        binding.config!!.bPendulumRange = setData[4]
 | 
			
		||||
        binding.config!!.bAverage = setData[5]
 | 
			
		||||
        binding.config!!.bRootWeight = setData[6]
 | 
			
		||||
        binding.config!!.bUseLimit = if (setData[7] == 0f) {
 | 
			
		||||
        config.bDamping = setData[0]
 | 
			
		||||
        config.bStiffness = setData[1]
 | 
			
		||||
        config.bSpring = setData[2]
 | 
			
		||||
        config.bPendulum = setData[3]
 | 
			
		||||
        config.bPendulumRange = setData[4]
 | 
			
		||||
        config.bAverage = setData[5]
 | 
			
		||||
        config.bRootWeight = setData[6]
 | 
			
		||||
        config.bUseLimit = if (setData[7] == 0f) {
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            binding.config!!.bLimitXx = setData[8]
 | 
			
		||||
            binding.config!!.bLimitXy = setData[9]
 | 
			
		||||
            binding.config!!.bLimitYx = setData[10]
 | 
			
		||||
            binding.config!!.bLimitYy = setData[11]
 | 
			
		||||
            binding.config!!.bLimitZx = setData[12]
 | 
			
		||||
            binding.config!!.bLimitZy = setData[13]
 | 
			
		||||
            config.bLimitXx = setData[8]
 | 
			
		||||
            config.bLimitXy = setData[9]
 | 
			
		||||
            config.bLimitYx = setData[10]
 | 
			
		||||
            config.bLimitYy = setData[11]
 | 
			
		||||
            config.bLimitZx = setData[12]
 | 
			
		||||
            config.bLimitZy = setData[13]
 | 
			
		||||
            true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        binding.config!!.bUseArmCorrection = true
 | 
			
		||||
        config.bUseArmCorrection = true
 | 
			
		||||
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPCheckBuiltInAssetsChanged(value: Boolean) {
 | 
			
		||||
        programConfig.checkBuiltInAssets = value
 | 
			
		||||
        if (value) {
 | 
			
		||||
            programConfig.cleanLocalAssets = false
 | 
			
		||||
        }
 | 
			
		||||
        saveProgramConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPUseRemoteAssetsChanged(value: Boolean) {
 | 
			
		||||
        programConfig.useRemoteAssets = value
 | 
			
		||||
        if (value) {
 | 
			
		||||
            programConfig.checkBuiltInAssets = false
 | 
			
		||||
            programConfig.cleanLocalAssets = false
 | 
			
		||||
            programConfig.useAPIAssets = false
 | 
			
		||||
        }
 | 
			
		||||
        saveProgramConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPCleanLocalAssetsChanged(value: Boolean) {
 | 
			
		||||
        programConfig.cleanLocalAssets = value
 | 
			
		||||
        if (value) {
 | 
			
		||||
            programConfig.useRemoteAssets = false
 | 
			
		||||
            programConfig.useAPIAssets = false
 | 
			
		||||
            programConfig.checkBuiltInAssets = false
 | 
			
		||||
        }
 | 
			
		||||
        saveProgramConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPDelRemoteAfterUpdateChanged(value: Boolean) {
 | 
			
		||||
        programConfig.delRemoteAfterUpdate = value
 | 
			
		||||
        saveProgramConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPTransRemoteZipUrlChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        programConfig.transRemoteZipUrl = s.toString()
 | 
			
		||||
        saveProgramConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mainPageAssetsViewDataUpdate(downloadAbleState: Boolean?, downloadProgressState: Float?,
 | 
			
		||||
                                              localResourceVersionState: String?, errorString: String?,
 | 
			
		||||
                                              localAPIResourceVersion: String?) {
 | 
			
		||||
        downloadAbleState?.let { programConfigViewModel.downloadAbleState.value = it }
 | 
			
		||||
        downloadProgressState?.let{ programConfigViewModel.downloadProgressState.value = it }
 | 
			
		||||
        localResourceVersionState?.let{ programConfigViewModel.localResourceVersionState.value = it }
 | 
			
		||||
        errorString?.let{ programConfigViewModel.errorStringState.value = it }
 | 
			
		||||
        localAPIResourceVersion?.let{ programConfigViewModel.localAPIResourceVersionState.value = it }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPUseAPIAssetsChanged(value: Boolean) {
 | 
			
		||||
        programConfig.useAPIAssets = value
 | 
			
		||||
        if (value) {
 | 
			
		||||
            programConfig.checkBuiltInAssets = false
 | 
			
		||||
            programConfig.useRemoteAssets = false
 | 
			
		||||
            programConfig.cleanLocalAssets = false
 | 
			
		||||
        }
 | 
			
		||||
        saveProgramConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPUseAPIAssetsURLChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        programConfig.useAPIAssetsURL = s.toString()
 | 
			
		||||
        saveProgramConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mainUIConfirmStatUpdate(isShow: Boolean?, title: String?, content: String?,
 | 
			
		||||
        onConfirm: (() -> Unit)?, onCancel: (() -> Unit)?
 | 
			
		||||
    ) {
 | 
			
		||||
        val orig = programConfigViewModel.mainUIConfirmState.value
 | 
			
		||||
        isShow?.let {
 | 
			
		||||
            if (orig.isShow && it) {
 | 
			
		||||
                Log.e(TAG, "Duplicate mainUIConfirmStat")
 | 
			
		||||
            }
 | 
			
		||||
            orig.isShow = it
 | 
			
		||||
        }
 | 
			
		||||
        title?.let { orig.title = it }
 | 
			
		||||
        content?.let { orig.content = it }
 | 
			
		||||
        onConfirm?.let { orig.onConfirm = {
 | 
			
		||||
            try {
 | 
			
		||||
                it()
 | 
			
		||||
            }
 | 
			
		||||
            finally {
 | 
			
		||||
                mainUIConfirmStatUpdate(isShow = false)
 | 
			
		||||
            }
 | 
			
		||||
        } }
 | 
			
		||||
        onCancel?.let { orig.onCancel = {
 | 
			
		||||
            try {
 | 
			
		||||
                it()
 | 
			
		||||
            }
 | 
			
		||||
            finally {
 | 
			
		||||
                mainUIConfirmStatUpdate(isShow = false)
 | 
			
		||||
            }
 | 
			
		||||
        } }
 | 
			
		||||
        orig.p = false
 | 
			
		||||
        programConfigViewModel.mainUIConfirmState.value = orig.copy(p = true)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,25 +8,37 @@ import android.content.Context
 | 
			
		|||
import android.content.Intent
 | 
			
		||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.os.Handler
 | 
			
		||||
import android.os.Looper
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
import android.view.MotionEvent
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import com.bytedance.shadowhook.ShadowHook
 | 
			
		||||
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
 | 
			
		||||
import de.robv.android.xposed.IXposedHookLoadPackage
 | 
			
		||||
import de.robv.android.xposed.IXposedHookZygoteInit
 | 
			
		||||
import de.robv.android.xposed.XC_MethodHook
 | 
			
		||||
import de.robv.android.xposed.XposedBridge
 | 
			
		||||
import de.robv.android.xposed.XposedHelpers
 | 
			
		||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
import android.view.MotionEvent
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import com.google.gson.Gson
 | 
			
		||||
import de.robv.android.xposed.XposedBridge
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import kotlinx.coroutines.DelicateCoroutinesApi
 | 
			
		||||
import kotlinx.coroutines.GlobalScope
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import kotlinx.coroutines.isActive
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
import kotlin.system.measureTimeMillis
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.localizationFilesDir
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.json
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.game_attach.InitProgressUI
 | 
			
		||||
 | 
			
		||||
val TAG = "GakumasLocalify"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -40,8 +52,24 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
    private var gkmsDataInited = false
 | 
			
		||||
 | 
			
		||||
    private var getConfigError: Exception? = null
 | 
			
		||||
    private var externalFilesChecked: Boolean = false
 | 
			
		||||
    private var gameActivity: Activity? = null
 | 
			
		||||
 | 
			
		||||
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
 | 
			
		||||
//        if (lpparam.packageName == "io.github.chinosk.gakumas.localify") {
 | 
			
		||||
//            XposedHelpers.findAndHookMethod(
 | 
			
		||||
//                "io.github.chinosk.gakumas.localify.MainActivity",
 | 
			
		||||
//                lpparam.classLoader,
 | 
			
		||||
//                "showToast",
 | 
			
		||||
//                String::class.java,
 | 
			
		||||
//                object : XC_MethodHook() {
 | 
			
		||||
//                    override fun beforeHookedMethod(param: MethodHookParam) {
 | 
			
		||||
//                        Log.d(TAG, "beforeHookedMethod hooked: ${param.args}")
 | 
			
		||||
//                    }
 | 
			
		||||
//                }
 | 
			
		||||
//            )
 | 
			
		||||
//        }
 | 
			
		||||
 | 
			
		||||
        if (lpparam.packageName != targetPackageName) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -112,6 +140,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
                super.beforeHookedMethod(param)
 | 
			
		||||
                Log.d(TAG, "onStart")
 | 
			
		||||
                val currActivity = param.thisObject as Activity
 | 
			
		||||
                gameActivity = currActivity
 | 
			
		||||
                if (getConfigError != null) {
 | 
			
		||||
                    showGetConfigFailed(currActivity)
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +154,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
            override fun beforeHookedMethod(param: MethodHookParam) {
 | 
			
		||||
                Log.d(TAG, "onResume")
 | 
			
		||||
                val currActivity = param.thisObject as Activity
 | 
			
		||||
                gameActivity = currActivity
 | 
			
		||||
                if (getConfigError != null) {
 | 
			
		||||
                    showGetConfigFailed(currActivity)
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -163,7 +193,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
                        requestConfig(app.applicationContext)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    FilesChecker.initAndCheck(app.filesDir, modulePath)
 | 
			
		||||
                    FilesChecker.initDir(app.filesDir, modulePath)
 | 
			
		||||
                    initHook(
 | 
			
		||||
                        "${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
 | 
			
		||||
                        File(
 | 
			
		||||
| 
						 | 
				
			
			@ -175,28 +205,151 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
                    alreadyInitialized = true
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
        startLoop()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @OptIn(DelicateCoroutinesApi::class)
 | 
			
		||||
    private fun startLoop() {
 | 
			
		||||
        GlobalScope.launch {
 | 
			
		||||
            val interval = 1000L / 30
 | 
			
		||||
            var lastFrameStartInit = NativeInitProgress.startInit
 | 
			
		||||
            val initProgressUI = InitProgressUI()
 | 
			
		||||
 | 
			
		||||
            while (isActive) {
 | 
			
		||||
                val timeTaken = measureTimeMillis {
 | 
			
		||||
                    val returnValue = pluginCallbackLooper()  // plugin main thread loop
 | 
			
		||||
                    if (returnValue == 9) {
 | 
			
		||||
                        NativeInitProgress.startInit = true
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (NativeInitProgress.startInit) {  // if init, update data
 | 
			
		||||
                        NativeInitProgress.pluginInitProgressLooper(NativeInitProgress)
 | 
			
		||||
                        gameActivity?.let { initProgressUI.updateData(it) }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ((gameActivity != null) && (lastFrameStartInit != NativeInitProgress.startInit)) {  // change status
 | 
			
		||||
                        if (NativeInitProgress.startInit) {
 | 
			
		||||
                            initProgressUI.createView(gameActivity!!)
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            initProgressUI.finishLoad(gameActivity!!)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    lastFrameStartInit = NativeInitProgress.startInit
 | 
			
		||||
                }
 | 
			
		||||
                delay(interval - timeTaken)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun initGkmsConfig(activity: Activity) {
 | 
			
		||||
        val intent = activity.intent
 | 
			
		||||
        val gkmsData = intent.getStringExtra("gkmsData")
 | 
			
		||||
        val programData = intent.getStringExtra("localData")
 | 
			
		||||
        if (gkmsData != null) {
 | 
			
		||||
            val readVersion = intent.getStringExtra("lVerName")
 | 
			
		||||
            checkPluginVersion(activity, readVersion)
 | 
			
		||||
 | 
			
		||||
            gkmsDataInited = true
 | 
			
		||||
            val initConfig = try {
 | 
			
		||||
                Gson().fromJson(gkmsData, GakumasConfig::class.java)
 | 
			
		||||
                json.decodeFromString<GakumasConfig>(gkmsData)
 | 
			
		||||
            }
 | 
			
		||||
            catch (e: Exception) {
 | 
			
		||||
                null
 | 
			
		||||
            }
 | 
			
		||||
            val programConfig = try {
 | 
			
		||||
                if (programData == null) {
 | 
			
		||||
                    ProgramConfig()
 | 
			
		||||
                } else {
 | 
			
		||||
                    json.decodeFromString<ProgramConfig>(programData)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (e: Exception) {
 | 
			
		||||
                null
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 清理本地文件
 | 
			
		||||
            if (programConfig?.cleanLocalAssets == true) {
 | 
			
		||||
                FilesChecker.cleanAssets()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 检查 files 版本和 assets 版本并更新
 | 
			
		||||
            if (programConfig?.checkBuiltInAssets == true) {
 | 
			
		||||
                FilesChecker.initAndCheck(activity.filesDir, modulePath)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 强制导出 assets 文件
 | 
			
		||||
            if (initConfig?.forceExportResource == true) {
 | 
			
		||||
                FilesChecker.updateFiles()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 使用热更新文件
 | 
			
		||||
            if ((programConfig?.useRemoteAssets == true) || (programConfig?.useAPIAssets == true)) {
 | 
			
		||||
                // val dataUri = intent.data
 | 
			
		||||
                val dataUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
 | 
			
		||||
                    intent.getParcelableExtra("resource_file", Uri::class.java)
 | 
			
		||||
                } else {
 | 
			
		||||
                    @Suppress("DEPRECATION")
 | 
			
		||||
                    intent.getParcelableExtra<Uri>("resource_file")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (dataUri != null) {
 | 
			
		||||
                    if (!externalFilesChecked) {
 | 
			
		||||
                        externalFilesChecked = true
 | 
			
		||||
                        // Log.d(TAG, "dataUri: $dataUri")
 | 
			
		||||
                        FileHotUpdater.updateFilesFromZip(activity, dataUri, activity.filesDir,
 | 
			
		||||
                            programConfig.delRemoteAfterUpdate)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else if (programConfig.useAPIAssets) {
 | 
			
		||||
                    if (!File(activity.filesDir, localizationFilesDir).exists() &&
 | 
			
		||||
                        (initConfig?.forceExportResource == false)) {
 | 
			
		||||
                        // 使用 API 资源,不检查内置,API 资源无效,且游戏内没有插件数据时,释放内置数据
 | 
			
		||||
                        FilesChecker.initAndCheck(activity.filesDir, modulePath)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            loadConfig(gkmsData)
 | 
			
		||||
            Log.d(TAG, "gkmsData: $gkmsData")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun checkPluginVersion(activity: Activity, readVersion: String?) {
 | 
			
		||||
        val buildVersionName = BuildConfig.VERSION_NAME
 | 
			
		||||
        Log.i(TAG, "Checking Plugin Version: Build: $buildVersionName, Request: $readVersion")
 | 
			
		||||
        if (readVersion?.trim() == buildVersionName.trim()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val builder = AlertDialog.Builder(activity)
 | 
			
		||||
        val infoBuilder = AlertDialog.Builder(activity)
 | 
			
		||||
        builder.setTitle("Warning")
 | 
			
		||||
        builder.setCancelable(false)
 | 
			
		||||
        builder.setMessage(when (getCurrentLanguage(activity)) {
 | 
			
		||||
            "zh" -> "检测到插件版本不一致\n内置版本: $buildVersionName\n请求版本: $readVersion\n\n这可能是使用了 LSPatch 的集成模式,仅更新了插件本体,未重新修补游戏导致的。请使用 $readVersion 版本的插件重新修补或使用本地模式。"
 | 
			
		||||
            else -> "Detected plugin version mismatch\nBuilt-in version: $buildVersionName\nRequested version: $readVersion\n\nThis may be caused by using the LSPatch integration mode, where only the plugin itself was updated without re-patching the game. Please re-patch the game using the $readVersion version of the plugin or use the local mode."
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        builder.setPositiveButton("OK") { dialog, _ ->
 | 
			
		||||
            dialog.dismiss()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        builder.setNegativeButton("Exit") { dialog, _ ->
 | 
			
		||||
            dialog.dismiss()
 | 
			
		||||
            activity.finishAffinity()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val dialog = builder.create()
 | 
			
		||||
 | 
			
		||||
        infoBuilder.setOnCancelListener {
 | 
			
		||||
            dialog.show()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        dialog.show()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun showGetConfigFailedImpl(activity: Context, title: String, msg: String, infoButton: String, dlButton: String, okButton: String) {
 | 
			
		||||
        if (getConfigError == null) return
 | 
			
		||||
        val builder = AlertDialog.Builder(activity)
 | 
			
		||||
| 
						 | 
				
			
			@ -273,7 +426,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
    fun requestConfig(activity: Context) {
 | 
			
		||||
        try {
 | 
			
		||||
            val intent = Intent().apply {
 | 
			
		||||
                setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.MainActivity")
 | 
			
		||||
                setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.TranslucentActivity")
 | 
			
		||||
                putExtra("gkmsData", "requestConfig")
 | 
			
		||||
                flags = FLAG_ACTIVITY_NEW_TASK
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -337,6 +490,9 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
                Log.e(TAG, "showToast: $message failed: applicationContext is null")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @JvmStatic
 | 
			
		||||
        external fun pluginCallbackLooper(): Int
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,166 +1,234 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewTreeObserver
 | 
			
		||||
import android.widget.ScrollView
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import androidx.databinding.DataBindingUtil
 | 
			
		||||
import com.google.android.material.button.MaterialButton
 | 
			
		||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
			
		||||
import com.google.gson.Gson
 | 
			
		||||
import com.google.gson.JsonSyntaxException
 | 
			
		||||
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
 | 
			
		||||
import androidx.activity.ComponentActivity
 | 
			
		||||
import androidx.activity.compose.setContent
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.State
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import androidx.lifecycle.ViewModelProvider
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.RemoteAPIFilesChecker
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuApi
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.json
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ConfirmStateModel
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModel
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModelFactory
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.pages.MainUI
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.asStateFlow
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import java.io.File
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainActivity : AppCompatActivity(), ConfigUpdateListener {
 | 
			
		||||
    override lateinit var binding: ActivityMainBinding
 | 
			
		||||
    private val TAG = "GakumasLocalify"
 | 
			
		||||
class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableActivity<MainActivity> {
 | 
			
		||||
    override lateinit var config: GakumasConfig
 | 
			
		||||
    override lateinit var programConfig: ProgramConfig
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        setContentView(R.layout.activity_main)
 | 
			
		||||
    override lateinit var factory: UserConfigViewModelFactory
 | 
			
		||||
    override lateinit var viewModel: UserConfigViewModel
 | 
			
		||||
 | 
			
		||||
        binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
 | 
			
		||||
        loadConfig()
 | 
			
		||||
        binding.listener = this
 | 
			
		||||
    override lateinit var programConfigFactory: ProgramConfigViewModelFactory
 | 
			
		||||
    override lateinit var programConfigViewModel: ProgramConfigViewModel
 | 
			
		||||
 | 
			
		||||
        val requestData = intent.getStringExtra("gkmsData")
 | 
			
		||||
        if (requestData != null) {
 | 
			
		||||
            if (requestData == "requestConfig") {
 | 
			
		||||
                onClickStartGame()
 | 
			
		||||
                finish()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        showVersion()
 | 
			
		||||
 | 
			
		||||
        val scrollView: ScrollView = findViewById(R.id.scrollView)
 | 
			
		||||
        scrollView.viewTreeObserver.addOnScrollChangedListener { onScrollChanged() }
 | 
			
		||||
        onScrollChanged()
 | 
			
		||||
 | 
			
		||||
        val coordinatorLayout = findViewById<View>(R.id.coordinatorLayout)
 | 
			
		||||
        coordinatorLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
 | 
			
		||||
            override fun onGlobalLayout() {
 | 
			
		||||
                onScrollChanged()
 | 
			
		||||
                coordinatorLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onClickStartGame() {
 | 
			
		||||
        val intent = Intent().apply {
 | 
			
		||||
            setClassName("com.bandainamcoent.idolmaster_gakuen", "com.google.firebase.MessagingUnityPlayerActivity")
 | 
			
		||||
            putExtra("gkmsData", getConfigContent())
 | 
			
		||||
            flags = Intent.FLAG_ACTIVITY_NEW_TASK
 | 
			
		||||
        }
 | 
			
		||||
        startActivity(intent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun onScrollChanged() {
 | 
			
		||||
        val fab: FloatingActionButton = findViewById(R.id.fabStartGame)
 | 
			
		||||
        val startGameButton: MaterialButton = findViewById(R.id.StartGameButton)
 | 
			
		||||
        val scrollView: ScrollView = findViewById(R.id.scrollView)
 | 
			
		||||
 | 
			
		||||
        val location = IntArray(2)
 | 
			
		||||
        startGameButton.getLocationOnScreen(location)
 | 
			
		||||
        val buttonTop = location[1]
 | 
			
		||||
        val buttonBottom = buttonTop + startGameButton.height
 | 
			
		||||
 | 
			
		||||
        val scrollViewLocation = IntArray(2)
 | 
			
		||||
        scrollView.getLocationOnScreen(scrollViewLocation)
 | 
			
		||||
        val scrollViewTop = scrollViewLocation[1]
 | 
			
		||||
        val scrollViewBottom = scrollViewTop + scrollView.height
 | 
			
		||||
 | 
			
		||||
        val isButtonVisible = buttonTop >= scrollViewTop && buttonBottom <= scrollViewBottom
 | 
			
		||||
 | 
			
		||||
        if (isButtonVisible) {
 | 
			
		||||
            fab.hide()
 | 
			
		||||
        } else {
 | 
			
		||||
            fab.show()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun showToast(message: String) {
 | 
			
		||||
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getConfigContent(): String {
 | 
			
		||||
        val configFile = File(filesDir, "gkms-config.json")
 | 
			
		||||
        return if (configFile.exists()) {
 | 
			
		||||
            configFile.readText()
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            showToast("检测到第一次启动,初始化配置文件...")
 | 
			
		||||
            "{}"
 | 
			
		||||
        }
 | 
			
		||||
    fun gotoPatchActivity() {
 | 
			
		||||
        val intent = Intent(this, PatchActivity::class.java)
 | 
			
		||||
        startActivity(intent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun saveConfig() {
 | 
			
		||||
        try {
 | 
			
		||||
            config.pf = false
 | 
			
		||||
            viewModel.configState.value = config.copy( pf = true )  // 更新 UI
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: RuntimeException) {
 | 
			
		||||
            Log.d(TAG, e.toString())
 | 
			
		||||
        }
 | 
			
		||||
        val configFile = File(filesDir, "gkms-config.json")
 | 
			
		||||
        configFile.writeText(Gson().toJson(binding.config!!))
 | 
			
		||||
        configFile.writeText(json.encodeToString(config))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    private fun showVersion() {
 | 
			
		||||
        val titleLabel = findViewById<TextView>(R.id.textViewTitle)
 | 
			
		||||
        val versionLabel = findViewById<TextView>(R.id.textViewResVersion)
 | 
			
		||||
        var versionText = "unknown"
 | 
			
		||||
    override fun saveProgramConfig() {
 | 
			
		||||
        try {
 | 
			
		||||
            programConfig.p = false
 | 
			
		||||
            programConfigViewModel.configState.value = programConfig.copy( p = true )  // 更新 UI
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: RuntimeException) {
 | 
			
		||||
            Log.d(TAG, e.toString())
 | 
			
		||||
        }
 | 
			
		||||
        val configFile = File(filesDir, "localify-config.json")
 | 
			
		||||
        configFile.writeText(json.encodeToString(programConfig))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getVersion(): List<String> {
 | 
			
		||||
        var versionText = ""
 | 
			
		||||
        var resVersionText = "unknown"
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt")
 | 
			
		||||
            versionText = FilesChecker.convertToString(stream)
 | 
			
		||||
            resVersionText = FilesChecker.convertToString(stream)
 | 
			
		||||
 | 
			
		||||
            if (programConfig.useAPIAssets) {
 | 
			
		||||
                RemoteAPIFilesChecker.getLocalVersion(this)?.let { resVersionText = it }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val packInfo = packageManager.getPackageInfo(packageName, 0)
 | 
			
		||||
            val version = packInfo.versionName
 | 
			
		||||
            val versionCode = packInfo.longVersionCode
 | 
			
		||||
            titleLabel.text = "${titleLabel.text} $version ($versionCode)"
 | 
			
		||||
            versionText = "$version ($versionCode)"
 | 
			
		||||
        }
 | 
			
		||||
        catch (_: Exception) {}
 | 
			
		||||
        versionLabel.text = "Assets Version: $versionText"
 | 
			
		||||
 | 
			
		||||
        return listOf(versionText, resVersionText)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun loadConfig() {
 | 
			
		||||
        val configStr = getConfigContent()
 | 
			
		||||
        binding.config = try {
 | 
			
		||||
            Gson().fromJson(configStr, GakumasConfig::class.java)
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: JsonSyntaxException) {
 | 
			
		||||
            showToast("配置文件异常,已重置: $e")
 | 
			
		||||
            Gson().fromJson("{}", GakumasConfig::class.java)
 | 
			
		||||
        }
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun checkConfigAndUpdateView() {
 | 
			
		||||
        binding.config = binding.config
 | 
			
		||||
        binding.notifyChange()
 | 
			
		||||
    fun openUrl(url: String) {
 | 
			
		||||
        val webpage = Uri.parse(url)
 | 
			
		||||
        val intent = Intent(Intent.ACTION_VIEW, webpage)
 | 
			
		||||
        startActivity(intent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pushKeyEvent(event: KeyEvent): Boolean {
 | 
			
		||||
        return dispatchKeyEvent(event)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("RestrictedApi")
 | 
			
		||||
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
 | 
			
		||||
        // Log.d(TAG, "${event.keyCode}, ${event.action}")
 | 
			
		||||
        if (MainKeyEventDispatcher.checkDbgKey(event.keyCode, event.action)) {
 | 
			
		||||
            val origDbg = binding.config?.dbgMode
 | 
			
		||||
            if (origDbg != null) {
 | 
			
		||||
                binding.config!!.dbgMode = !origDbg
 | 
			
		||||
                checkConfigAndUpdateView()
 | 
			
		||||
                saveConfig()
 | 
			
		||||
                showToast("TestMode: ${!origDbg}")
 | 
			
		||||
            }
 | 
			
		||||
            val origDbg = config.dbgMode
 | 
			
		||||
            config.dbgMode = !origDbg
 | 
			
		||||
            checkConfigAndUpdateView()
 | 
			
		||||
            saveConfig()
 | 
			
		||||
            showToast("TestMode: ${!origDbg}")
 | 
			
		||||
        }
 | 
			
		||||
        return if (event.action == 1145) true else super.dispatchKeyEvent(event)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        loadConfig()
 | 
			
		||||
 | 
			
		||||
        factory = UserConfigViewModelFactory(config)
 | 
			
		||||
        viewModel = ViewModelProvider(this, factory)[UserConfigViewModel::class.java]
 | 
			
		||||
 | 
			
		||||
        programConfigFactory = ProgramConfigViewModelFactory(programConfig,
 | 
			
		||||
            FileHotUpdater.getZipResourceVersion(File(filesDir, "update_trans.zip").absolutePath).toString()
 | 
			
		||||
        )
 | 
			
		||||
        programConfigViewModel = ViewModelProvider(this, programConfigFactory)[ProgramConfigViewModel::class.java]
 | 
			
		||||
 | 
			
		||||
        ShizukuApi.init()
 | 
			
		||||
 | 
			
		||||
        setContent {
 | 
			
		||||
            GakumasLocalifyTheme(dynamicColor = false, darkTheme = false) {
 | 
			
		||||
                MainUI(context = this)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getConfigState(context: MainActivity?, previewData: GakumasConfig?): State<GakumasConfig> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.viewModel.config.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow(previewData!!)
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramConfigState(context: MainActivity?, previewData: ProgramConfig? = null): State<ProgramConfig> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.config.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow(previewData ?: ProgramConfig())
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramDownloadState(context: MainActivity?): State<Float> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.downloadProgress.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow(0f)
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramDownloadAbleState(context: MainActivity?): State<Boolean> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.downloadAble.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow(true)
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramLocalResourceVersionState(context: MainActivity?): State<String> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.localResourceVersion.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow("null")
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramLocalAPIResourceVersionState(context: MainActivity?): State<String> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.localAPIResourceVersion.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow("null")
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramDownloadErrorStringState(context: MainActivity?): State<String> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.errorString.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow("")
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getMainUIConfirmState(context: MainActivity?, previewData: ConfirmStateModel? = null): State<ConfirmStateModel> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.mainUIConfirm.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow(previewData ?: ConfirmStateModel())
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,688 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify
 | 
			
		||||
 | 
			
		||||
import android.Manifest
 | 
			
		||||
import android.content.ContentValues
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.pm.PackageInstaller
 | 
			
		||||
import android.content.pm.PackageManager
 | 
			
		||||
import android.media.MediaScannerConnection
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.os.Environment
 | 
			
		||||
import android.provider.MediaStore
 | 
			
		||||
import android.provider.OpenableColumns
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.activity.ComponentActivity
 | 
			
		||||
import androidx.activity.compose.setContent
 | 
			
		||||
import androidx.activity.result.IntentSenderRequest
 | 
			
		||||
import androidx.activity.result.contract.ActivityResultContracts
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import androidx.core.content.FileProvider
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.IOnShell
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.LSPatchUtils
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuApi
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuShell
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.InstallDiag
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.pages.PatchPage
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import org.lsposed.patch.LSPatch
 | 
			
		||||
import org.lsposed.patch.util.Logger
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.FileInputStream
 | 
			
		||||
import java.io.FileOutputStream
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
import java.io.OutputStream
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
import java.nio.file.attribute.PosixFilePermissions
 | 
			
		||||
import java.util.concurrent.CountDownLatch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface PatchCallback {
 | 
			
		||||
    fun onLog(message: String, isError: Boolean = false)
 | 
			
		||||
    fun onSuccess(outFiles: List<File>)
 | 
			
		||||
    fun onFailed(msg: String, exception: Throwable? = null)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val patchTag = "${TAG}-Patcher"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
open class PatchLogger : Logger() {
 | 
			
		||||
    override fun d(msg: String) {
 | 
			
		||||
        if (this.verbose) {
 | 
			
		||||
            Log.d(patchTag, msg)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun i(msg: String) {
 | 
			
		||||
        Log.i(patchTag, msg)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun e(msg: String) {
 | 
			
		||||
        Log.e(patchTag, msg)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LSPatchExt(outputDir: String, isDebuggable: Boolean, localMode: Boolean, logger: Logger) : LSPatch(logger, "123.apk --debuggable --manager -l 2") {
 | 
			
		||||
    init {
 | 
			
		||||
        val parentClass = LSPatch::class.java
 | 
			
		||||
        // val apkPathsField = parentClass.getDeclaredField("apkPaths")
 | 
			
		||||
        val outputPathField = parentClass.getDeclaredField("outputPath")
 | 
			
		||||
        val forceOverwriteField = parentClass.getDeclaredField("forceOverwrite")
 | 
			
		||||
        val debuggableFlagField = parentClass.getDeclaredField("debuggableFlag")
 | 
			
		||||
        val useManagerField = parentClass.getDeclaredField("useManager")
 | 
			
		||||
 | 
			
		||||
        // apkPathsField.isAccessible = true
 | 
			
		||||
        outputPathField.isAccessible = true
 | 
			
		||||
        forceOverwriteField.isAccessible = true
 | 
			
		||||
        debuggableFlagField.isAccessible = true
 | 
			
		||||
        useManagerField.isAccessible = true
 | 
			
		||||
 | 
			
		||||
        // apkPathsField.set(this, apkPaths)
 | 
			
		||||
        forceOverwriteField.set(this, true)
 | 
			
		||||
        outputPathField.set(this, outputDir)
 | 
			
		||||
        debuggableFlagField.set(this, isDebuggable)
 | 
			
		||||
        useManagerField.set(this, localMode)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setModules(modules: List<String>) {
 | 
			
		||||
        val parentClass = LSPatch::class.java
 | 
			
		||||
        val modulesField = parentClass.getDeclaredField("modules")
 | 
			
		||||
        modulesField.isAccessible = true
 | 
			
		||||
        modulesField.set(this, modules)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PatchActivity : ComponentActivity() {
 | 
			
		||||
    private lateinit var outputDir: String
 | 
			
		||||
    private var mOutFiles: List<File> = listOf()
 | 
			
		||||
    private var reservePatchFiles: Boolean = false
 | 
			
		||||
    var patchCallback: PatchCallback? = null
 | 
			
		||||
 | 
			
		||||
    private val writePermissionLauncher = registerForActivityResult(
 | 
			
		||||
        ActivityResultContracts.StartIntentSenderForResult()
 | 
			
		||||
    ) { result ->
 | 
			
		||||
        if (result.resultCode != RESULT_OK) {
 | 
			
		||||
            Toast.makeText(this, "Permission Request Failed.", Toast.LENGTH_SHORT).show()
 | 
			
		||||
            finish()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val writePermissionLauncherQ = registerForActivityResult(
 | 
			
		||||
        ActivityResultContracts.RequestPermission()
 | 
			
		||||
    ) { isGranted ->
 | 
			
		||||
        if (!isGranted) {
 | 
			
		||||
            Toast.makeText(this, "Permission Request Failed.", Toast.LENGTH_SHORT).show()
 | 
			
		||||
            finish()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun checkAndRequestWritePermission() {
 | 
			
		||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
 | 
			
		||||
            /*
 | 
			
		||||
            // 针对 API 级别 30 及以上使用 MediaStore.createWriteRequest
 | 
			
		||||
            val uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
 | 
			
		||||
            val intentSender = MediaStore.createWriteRequest(contentResolver, listOf(uri)).intentSender
 | 
			
		||||
            writePermissionLauncher.launch(IntentSenderRequest.Builder(intentSender).build())*/
 | 
			
		||||
        }
 | 
			
		||||
        else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
 | 
			
		||||
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
 | 
			
		||||
                != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
                // 请求 WRITE_EXTERNAL_STORAGE 权限
 | 
			
		||||
                writePermissionLauncherQ.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun writeFileToDownloadFolder(
 | 
			
		||||
        sourceFile: File,
 | 
			
		||||
        targetFolder: String,
 | 
			
		||||
        targetFileName: String
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
        val downloadDirectory = Environment.DIRECTORY_DOWNLOADS
 | 
			
		||||
        val relativePath = "$downloadDirectory/$targetFolder/"
 | 
			
		||||
        val resolver = contentResolver
 | 
			
		||||
 | 
			
		||||
        // 检查文件是否已经存在
 | 
			
		||||
        val existingUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
 | 
			
		||||
        val query = resolver.query(
 | 
			
		||||
            existingUri,
 | 
			
		||||
            arrayOf(MediaStore.Files.FileColumns._ID),
 | 
			
		||||
            "${MediaStore.Files.FileColumns.RELATIVE_PATH}=? AND ${MediaStore.Files.FileColumns.DISPLAY_NAME}=?",
 | 
			
		||||
            arrayOf(relativePath, targetFileName),
 | 
			
		||||
            null
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        query?.use {
 | 
			
		||||
            if (it.moveToFirst()) {
 | 
			
		||||
                // 如果文件存在,则删除
 | 
			
		||||
                val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
 | 
			
		||||
                val deleteUri = MediaStore.Files.getContentUri("external", id)
 | 
			
		||||
                resolver.delete(deleteUri, null, null)
 | 
			
		||||
                Log.d(patchTag, "query delete: $deleteUri")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val contentValues = ContentValues().apply {
 | 
			
		||||
            put(MediaStore.Downloads.DISPLAY_NAME, targetFileName)
 | 
			
		||||
            put(MediaStore.Downloads.MIME_TYPE, "application/octet-stream")
 | 
			
		||||
            put(MediaStore.Downloads.RELATIVE_PATH, relativePath)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
 | 
			
		||||
        Log.d(patchTag, "insert uri: $uri")
 | 
			
		||||
 | 
			
		||||
        if (uri == null) {
 | 
			
		||||
            val latch = CountDownLatch(1)
 | 
			
		||||
            val downloadDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
 | 
			
		||||
            val downloadSaveDirectory = File(downloadDirectory, targetFolder)
 | 
			
		||||
            val downloadSaveFile = File(downloadSaveDirectory, targetFileName)
 | 
			
		||||
            MediaScannerConnection.scanFile(this, arrayOf(downloadSaveFile.absolutePath),
 | 
			
		||||
                null
 | 
			
		||||
            ) { _, _ ->
 | 
			
		||||
                Log.d(patchTag, "scanFile finished.")
 | 
			
		||||
                latch.countDown()
 | 
			
		||||
            }
 | 
			
		||||
            latch.await()
 | 
			
		||||
            uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
 | 
			
		||||
            if (uri == null) {
 | 
			
		||||
                Log.e(patchTag, "uri is still null")
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return try {
 | 
			
		||||
            resolver.openOutputStream(uri)?.use { outputStream ->
 | 
			
		||||
                FileInputStream(sourceFile).use { inputStream ->
 | 
			
		||||
                    inputStream.copyTo(outputStream)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            contentValues.clear()
 | 
			
		||||
            contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
 | 
			
		||||
            resolver.update(uri, contentValues, null, null)
 | 
			
		||||
            true
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            resolver.delete(uri, null, null)
 | 
			
		||||
            e.printStackTrace()
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun deleteFileInDownloadFolder(targetFolder: String, targetFileName: String) {
 | 
			
		||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
 | 
			
		||||
            val selection =
 | 
			
		||||
                "${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaStore.MediaColumns.DISPLAY_NAME} = ?"
 | 
			
		||||
            val selectionArgs =
 | 
			
		||||
                arrayOf("${Environment.DIRECTORY_DOWNLOADS}/$targetFolder/", targetFileName)
 | 
			
		||||
 | 
			
		||||
            val uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
 | 
			
		||||
            contentResolver.delete(uri, selection, selectionArgs)
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "$targetFolder/$targetFileName")
 | 
			
		||||
            if (file.exists()) {
 | 
			
		||||
                if (file.delete()) {
 | 
			
		||||
                    // Toast.makeText(this, "文件已删除", Toast.LENGTH_SHORT).show()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun handleSelectedFile(uri: Uri) {
 | 
			
		||||
        val fileName = uri.path?.substringAfterLast('/')
 | 
			
		||||
        if (fileName != null) {
 | 
			
		||||
            Log.d(patchTag, fileName)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        outputDir = "${filesDir.absolutePath}/output"
 | 
			
		||||
        // ShizukuApi.init()
 | 
			
		||||
        checkAndRequestWritePermission()
 | 
			
		||||
 | 
			
		||||
        setContent {
 | 
			
		||||
            GakumasLocalifyTheme(dynamicColor = false, darkTheme = false) {
 | 
			
		||||
                val scope = rememberCoroutineScope()
 | 
			
		||||
                var installing by remember { mutableStateOf(false) }
 | 
			
		||||
 | 
			
		||||
                PatchPage() { apks, isPatchLocalMode, isPatchDebuggable, isReservePatchFiles, onFinish, onLog ->
 | 
			
		||||
                    reservePatchFiles = isReservePatchFiles
 | 
			
		||||
 | 
			
		||||
                    onClickPatch(apks, isPatchLocalMode, isPatchDebuggable, object : PatchCallback {
 | 
			
		||||
                        init {
 | 
			
		||||
                            patchCallback = this
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        override fun onLog(message: String, isError: Boolean) {
 | 
			
		||||
                            onLog(message, isError)
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        override fun onSuccess(outFiles: List<File>) {
 | 
			
		||||
                            // Handle success, e.g., notify user or update UI
 | 
			
		||||
                            Log.i(patchTag, "Patch succeeded: $outFiles")
 | 
			
		||||
                            onLog("Patch succeeded: $outFiles")
 | 
			
		||||
                            onFinish()
 | 
			
		||||
 | 
			
		||||
                            scope.launch {
 | 
			
		||||
                                mOutFiles = outFiles
 | 
			
		||||
                                installing = true
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        override fun onFailed(msg: String, exception: Throwable?) {
 | 
			
		||||
                            Log.i(patchTag, "Patch failed: $msg", exception)
 | 
			
		||||
                            onLog("Patch failed: $msg\n$exception", true)
 | 
			
		||||
                            LSPatchUtils.deleteDirectory(File(outputDir))
 | 
			
		||||
                            onFinish()
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (installing) InstallDiag(this@PatchActivity, mOutFiles, patchCallback, reservePatchFiles) { _, _ ->
 | 
			
		||||
                    installing = false
 | 
			
		||||
                    mOutFiles = listOf()
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun onClickPatch(apkPaths: List<Uri>, isLocalMode: Boolean, isDebuggable: Boolean, callback: PatchCallback) {
 | 
			
		||||
        var isPureApk = true
 | 
			
		||||
 | 
			
		||||
        for (i in apkPaths) {  // 判断是否全是apk
 | 
			
		||||
            val fileName = getFileName(i)
 | 
			
		||||
            if (fileName == null) {
 | 
			
		||||
                callback.onFailed("Get file name failed: $i")
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                if (!fileName.lowercase().endsWith(".apk")) {
 | 
			
		||||
                    isPureApk = false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (apkPaths.size != 1 && !isPureApk) {  // 多选,非全 apk
 | 
			
		||||
            callback.onFailed("Multiple selection files must be all apk files.")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isPureApk) {
 | 
			
		||||
            val apks: MutableList<File> = mutableListOf()
 | 
			
		||||
            // val apkPathStr: MutableList<String> = mutableListOf()
 | 
			
		||||
            for (i in apkPaths) {
 | 
			
		||||
                val apkFile = uriToFile(i)
 | 
			
		||||
                if (apkFile == null) {
 | 
			
		||||
                    callback.onFailed("Get file failed: $i")
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                apks.add(apkFile)
 | 
			
		||||
                // apkPathStr.add(apkFile.absolutePath)
 | 
			
		||||
            }
 | 
			
		||||
            patchApks(apks, isLocalMode, isDebuggable, callback)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val fileUri = apkPaths[0]
 | 
			
		||||
        val fileName = getFileName(fileUri)
 | 
			
		||||
        if (fileName == null) {
 | 
			
		||||
            callback.onFailed("Get file name failed: $fileUri")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        val lowerName = fileName.lowercase()
 | 
			
		||||
        if (!(lowerName.endsWith("apks") || lowerName.endsWith("xapk") || lowerName.endsWith("zip"))) {
 | 
			
		||||
            callback.onFailed("Unknown file: $fileName")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val inputStream: InputStream? = contentResolver.openInputStream(fileUri)
 | 
			
		||||
        if (inputStream == null) {
 | 
			
		||||
            callback.onFailed("Open file failed: $fileUri")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        val unzipCacheDir = File(cacheDir, "apks_unzip")
 | 
			
		||||
        if (unzipCacheDir.exists()) {
 | 
			
		||||
            LSPatchUtils.deleteDirectory(unzipCacheDir)
 | 
			
		||||
        }
 | 
			
		||||
        unzipCacheDir.mkdirs()
 | 
			
		||||
 | 
			
		||||
        CoroutineScope(Dispatchers.IO).launch {
 | 
			
		||||
            // FileHotUpdater.unzip(inputStream, unzipCacheDir.absolutePath)
 | 
			
		||||
            withContext(Dispatchers.Main) {
 | 
			
		||||
                callback.onLog("Unzipping...")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            LSPatchUtils.unzipXAPKWithProgress(inputStream, unzipCacheDir.absolutePath) { /*percent ->
 | 
			
		||||
                runOnUiThread {
 | 
			
		||||
                    Log.d(TAG, "unzip: $percent")
 | 
			
		||||
                }*/
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val files = unzipCacheDir.listFiles()
 | 
			
		||||
            if (files == null) {
 | 
			
		||||
                withContext(Dispatchers.Main) {
 | 
			
		||||
                    callback.onFailed("Can't get unzip files: $fileName")
 | 
			
		||||
                }
 | 
			
		||||
                return@launch
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            withContext(Dispatchers.Main) {
 | 
			
		||||
                callback.onLog("Unzip completed.")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val apks: MutableList<File> = mutableListOf()
 | 
			
		||||
            for (file in files) {
 | 
			
		||||
                if (file.isFile) {
 | 
			
		||||
                    if (file.name.lowercase().endsWith(".apk")) {
 | 
			
		||||
                        apks.add(file)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            patchApks(apks, isLocalMode, isDebuggable, callback) {
 | 
			
		||||
                LSPatchUtils.deleteDirectory(unzipCacheDir)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun patchApks(apks: List<File>, isLocalMode: Boolean, isDebuggable: Boolean,
 | 
			
		||||
                          callback: PatchCallback, onPatchEnd: (() -> Unit)? = null) {
 | 
			
		||||
 | 
			
		||||
        CoroutineScope(Dispatchers.IO).launch {
 | 
			
		||||
            try {
 | 
			
		||||
                val lspatch = LSPatchExt(outputDir, isDebuggable, isLocalMode, object : PatchLogger() {
 | 
			
		||||
                    override fun d(msg: String) {
 | 
			
		||||
                        super.d(msg)
 | 
			
		||||
                        runOnUiThread {
 | 
			
		||||
                            callback.onLog(msg)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    override fun i(msg: String) {
 | 
			
		||||
                        super.i(msg)
 | 
			
		||||
                        runOnUiThread {
 | 
			
		||||
                            callback.onLog(msg)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    override fun e(msg: String) {
 | 
			
		||||
                        super.e(msg)
 | 
			
		||||
                        runOnUiThread {
 | 
			
		||||
                            callback.onLog(msg, true)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                if (!isLocalMode) {
 | 
			
		||||
                    lspatch.setModules(listOf(applicationInfo.sourceDir))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                withContext(Dispatchers.Main) {
 | 
			
		||||
                    callback.onLog("Patching started.")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // lspatch.doCommandLine()
 | 
			
		||||
                val outBasePath = File(filesDir, "output")
 | 
			
		||||
                if (!outBasePath.exists()) {
 | 
			
		||||
                    outBasePath.mkdirs()
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                val outFiles: MutableList<File> = mutableListOf()
 | 
			
		||||
                for (i in apks) {
 | 
			
		||||
                    val outFile = File(outBasePath, "patch-${i.name}")
 | 
			
		||||
                    if (outFile.exists()) {
 | 
			
		||||
                        outFile.delete()
 | 
			
		||||
                    }
 | 
			
		||||
                    callback.onLog("Patching $i")
 | 
			
		||||
                    lspatch.patch(i, outFile)
 | 
			
		||||
                    i.delete()
 | 
			
		||||
                    outFiles.add(outFile)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                withContext(Dispatchers.Main) {
 | 
			
		||||
                    callback.onLog("Patching completed.")
 | 
			
		||||
                    callback.onSuccess(outFiles)
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e: Error) {
 | 
			
		||||
                // Log error and call the failure callback
 | 
			
		||||
                Log.e(patchTag, "Patch error", e)
 | 
			
		||||
                withContext(Dispatchers.Main) {
 | 
			
		||||
                    callback.onFailed("Patch error: ${e.message}", e)
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                // Log exception and call the failure callback
 | 
			
		||||
                Log.e(patchTag, "Patch exception", e)
 | 
			
		||||
                withContext(Dispatchers.Main) {
 | 
			
		||||
                    callback.onFailed("Patch exception: ${e.message}", e)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            finally {
 | 
			
		||||
                onPatchEnd?.let { it() }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getFileName(uri: Uri): String? {
 | 
			
		||||
        var fileName: String? = null
 | 
			
		||||
        val cursor = contentResolver.query(uri, null, null, null, null)
 | 
			
		||||
        cursor?.use {
 | 
			
		||||
            if (it.moveToFirst()) {
 | 
			
		||||
                fileName = it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return fileName
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun uriToFile(uri: Uri): File? {
 | 
			
		||||
        val fileName = getFileName(uri) ?: return null
 | 
			
		||||
        val file = File(cacheDir, fileName)
 | 
			
		||||
        try {
 | 
			
		||||
            val inputStream: InputStream? = contentResolver.openInputStream(uri)
 | 
			
		||||
            val outputStream: OutputStream = FileOutputStream(file)
 | 
			
		||||
            inputStream?.use { input ->
 | 
			
		||||
                outputStream.use { output ->
 | 
			
		||||
                    val buffer = ByteArray(4 * 1024) // 4KB
 | 
			
		||||
                    var read: Int
 | 
			
		||||
                    while (input.read(buffer).also { read = it } != -1) {
 | 
			
		||||
                        output.write(buffer, 0, read)
 | 
			
		||||
                    }
 | 
			
		||||
                    output.flush()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return file
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            e.printStackTrace()
 | 
			
		||||
        }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        fun getUriFromFile(context: Context, file: File): Uri {
 | 
			
		||||
            return FileProvider.getUriForFile(
 | 
			
		||||
                context,
 | 
			
		||||
                "io.github.chinosk.gakumas.localify.fileprovider",
 | 
			
		||||
                file
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun saveFileTo(apkFiles: List<File>, targetDirectory: File, isMove: Boolean,
 | 
			
		||||
                       enablePermission: Boolean): List<File> {
 | 
			
		||||
            val hasDirectory = if (!targetDirectory.exists()) {
 | 
			
		||||
                targetDirectory.mkdirs()
 | 
			
		||||
            } else {
 | 
			
		||||
                true
 | 
			
		||||
            }
 | 
			
		||||
            if (!hasDirectory) {
 | 
			
		||||
                throw NoSuchFileException(targetDirectory, reason = "check targetDirectory failed.")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (enablePermission) {
 | 
			
		||||
                try {
 | 
			
		||||
                    val origPermission = Files.getPosixFilePermissions(targetDirectory.toPath())
 | 
			
		||||
                    val requiredPermissions = PosixFilePermissions.fromString("rwxrwxrwx")
 | 
			
		||||
                    if (!origPermission.equals(requiredPermissions)) {
 | 
			
		||||
                        Files.setPosixFilePermissions(targetDirectory.toPath(), requiredPermissions)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (e: Exception) {
 | 
			
		||||
                    Log.e(TAG, "checkPosixFilePermissions failed.", e)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val movedFiles: MutableList<File> = mutableListOf()
 | 
			
		||||
            apkFiles.forEach { file ->
 | 
			
		||||
                val targetFile = File(targetDirectory, file.name)
 | 
			
		||||
                if (targetFile.exists()) targetFile.delete()
 | 
			
		||||
                file.copyTo(targetFile)
 | 
			
		||||
                movedFiles.add(targetFile)
 | 
			
		||||
                if (isMove) {
 | 
			
		||||
                    file.delete()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return movedFiles
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private fun generateNonce(size: Int): String {
 | 
			
		||||
            val nonceScope = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 | 
			
		||||
            val scopeSize = nonceScope.length
 | 
			
		||||
            val nonceItem: (Int) -> Char = { nonceScope[(scopeSize * Math.random()).toInt()] }
 | 
			
		||||
            return Array(size, nonceItem).joinToString("")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun saveFilesToDownload(context: PatchActivity, apkFiles: List<File>, targetFolder: String,
 | 
			
		||||
                                isMove: Boolean): List<String>? {
 | 
			
		||||
            val ret: MutableList<String> = mutableListOf()
 | 
			
		||||
            apkFiles.forEach { f ->
 | 
			
		||||
                val success = context.writeFileToDownloadFolder(f, targetFolder, f.name)
 | 
			
		||||
                if (success) {
 | 
			
		||||
                    ret.add(f.name)
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    val newName = "${generateNonce(6)}${f.name}"
 | 
			
		||||
                    val success2 = context.writeFileToDownloadFolder(f, targetFolder,
 | 
			
		||||
                        newName)
 | 
			
		||||
                    if (!success2) {
 | 
			
		||||
                        return null
 | 
			
		||||
                    }
 | 
			
		||||
                    ret.add(newName)
 | 
			
		||||
                }
 | 
			
		||||
                if (isMove) {
 | 
			
		||||
                    f.delete()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return ret
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        suspend fun installSplitApks(context: PatchActivity, apkFiles: List<File>, reservePatchFiles: Boolean,
 | 
			
		||||
                                     patchCallback: PatchCallback?): Pair<Int, String?> {
 | 
			
		||||
            Log.i(TAG, "Perform install patched apks")
 | 
			
		||||
            var status = PackageInstaller.STATUS_FAILURE
 | 
			
		||||
            var message: String? = null
 | 
			
		||||
 | 
			
		||||
            withContext(Dispatchers.IO) {
 | 
			
		||||
                runCatching {
 | 
			
		||||
                    val sdcardPath = Environment.getExternalStorageDirectory().path
 | 
			
		||||
                    val targetDirectory = File(sdcardPath, "Download/gkms_local_patch")
 | 
			
		||||
                    // val savedFiles = saveFileTo(apkFiles, targetDirectory, true, false)
 | 
			
		||||
 | 
			
		||||
                    val savedFileNames = saveFilesToDownload(context, apkFiles, "gkms_local_patch", true)
 | 
			
		||||
                    if (savedFileNames == null) {
 | 
			
		||||
                        status = PackageInstaller.STATUS_FAILURE
 | 
			
		||||
                        message = "Save files failed."
 | 
			
		||||
                        return@runCatching
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // patchCallback?.onLog("Patched files: $savedFiles")
 | 
			
		||||
                    patchCallback?.onLog("Patched files: $apkFiles")
 | 
			
		||||
 | 
			
		||||
                    if (!ShizukuApi.isPermissionGranted) {
 | 
			
		||||
                        status = PackageInstaller.STATUS_FAILURE
 | 
			
		||||
                        message = "Shizuku Not Ready."
 | 
			
		||||
                        // if (!reservePatchFiles) savedFiles.forEach { file -> if (file.exists()) file.delete() }
 | 
			
		||||
                        if (!reservePatchFiles) {
 | 
			
		||||
                            savedFileNames.forEach { f ->
 | 
			
		||||
                                context.deleteFileInDownloadFolder("gkms_local_patch", f)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        return@runCatching
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    val ioShell = object: IOnShell {
 | 
			
		||||
                        override fun onShellLine(msg: String) {
 | 
			
		||||
                            patchCallback?.onLog(msg)
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        override fun onShellError(msg: String) {
 | 
			
		||||
                            patchCallback?.onLog(msg, true)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (ShizukuApi.isPackageInstalledWithoutPatch("com.bandainamcoent.idolmaster_gakuen")) {
 | 
			
		||||
                        val uninstallShell = ShizukuShell(mutableListOf(), "pm uninstall com.bandainamcoent.idolmaster_gakuen", ioShell)
 | 
			
		||||
                        uninstallShell.exec()
 | 
			
		||||
                        uninstallShell.destroy()
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    val installDS = "/data/local/tmp/gkms_local_patch"
 | 
			
		||||
 | 
			
		||||
                    val action = if (reservePatchFiles) "cp" else "mv"
 | 
			
		||||
                    val copyFilesCmd: MutableList<String> = mutableListOf()
 | 
			
		||||
                    val movedFiles: MutableList<String> = mutableListOf()
 | 
			
		||||
                    savedFileNames.forEach { file ->
 | 
			
		||||
                        val movedFileName = "\"$installDS/${file}\""
 | 
			
		||||
                        movedFiles.add(movedFileName)
 | 
			
		||||
                        val dlSaveFileName = File(targetDirectory, file)
 | 
			
		||||
                        copyFilesCmd.add("$action ${dlSaveFileName.absolutePath} $movedFileName")
 | 
			
		||||
                    }
 | 
			
		||||
                    /*
 | 
			
		||||
                    savedFiles.forEach { file ->
 | 
			
		||||
                        val movedFileName = "$installDS/${file.name}"
 | 
			
		||||
                        movedFiles.add(movedFileName)
 | 
			
		||||
                        copyFilesCmd.add("$action ${file.absolutePath} $movedFileName")
 | 
			
		||||
                    }
 | 
			
		||||
                    */
 | 
			
		||||
                    val createDirCommand = "mkdir $installDS"
 | 
			
		||||
                    val moveFileCommand = "chmod 777 $installDS && " +
 | 
			
		||||
                            copyFilesCmd.joinToString(" && ")
 | 
			
		||||
                    Log.d(TAG, "moveFileCommand: $moveFileCommand")
 | 
			
		||||
 | 
			
		||||
                    ShizukuShell(mutableListOf(), createDirCommand, ioShell).exec().destroy()
 | 
			
		||||
 | 
			
		||||
                    val cpFileShell = ShizukuShell(mutableListOf(), moveFileCommand, ioShell)
 | 
			
		||||
                    cpFileShell.exec()
 | 
			
		||||
                    cpFileShell.destroy()
 | 
			
		||||
 | 
			
		||||
                    val installFiles = movedFiles.joinToString(" ")
 | 
			
		||||
                    val command = "pm install -r $installFiles && rm $installFiles"
 | 
			
		||||
                    Log.d(TAG, "shell: $command")
 | 
			
		||||
                    val sh = ShizukuShell(mutableListOf(), command, ioShell)
 | 
			
		||||
                    sh.exec()
 | 
			
		||||
                    sh.destroy()
 | 
			
		||||
 | 
			
		||||
                    status = PackageInstaller.STATUS_SUCCESS
 | 
			
		||||
                    message = "Done."
 | 
			
		||||
                }.onFailure { e ->
 | 
			
		||||
                    status = PackageInstaller.STATUS_FAILURE
 | 
			
		||||
                    message = e.stackTraceToString()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return Pair(status, message)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.activity.ComponentActivity
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TranslucentActivity : ComponentActivity(), IConfigurableActivity<TranslucentActivity> {
 | 
			
		||||
    override lateinit var config: GakumasConfig
 | 
			
		||||
    override lateinit var programConfig: ProgramConfig
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        loadConfig()
 | 
			
		||||
        val requestData = intent.getStringExtra("gkmsData")
 | 
			
		||||
        if (requestData != null) {
 | 
			
		||||
            if (requestData == "requestConfig") {
 | 
			
		||||
                onClickStartGame()
 | 
			
		||||
                finish()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,181 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.hookUtils
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import io.github.chinosk.gakumas.localify.GakumasHookMain
 | 
			
		||||
import io.github.chinosk.gakumas.localify.TAG
 | 
			
		||||
import java.io.BufferedReader
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.FileInputStream
 | 
			
		||||
import java.io.FileOutputStream
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
import java.io.InputStreamReader
 | 
			
		||||
import java.util.zip.ZipInputStream
 | 
			
		||||
 | 
			
		||||
object FileHotUpdater {
 | 
			
		||||
    private fun unzip(zipFile: InputStream, destDir: String, matchNamePrefix: String = "",
 | 
			
		||||
                      replaceMatchNamePrefix: String? = null) {
 | 
			
		||||
        val buffer = ByteArray(1024)
 | 
			
		||||
        try {
 | 
			
		||||
            val folder = File(destDir)
 | 
			
		||||
            if (!folder.exists()) {
 | 
			
		||||
                folder.mkdir()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val zipIn = ZipInputStream(zipFile)
 | 
			
		||||
 | 
			
		||||
            var entry = zipIn.nextEntry
 | 
			
		||||
            while (entry != null) {
 | 
			
		||||
                var writeEntryName = entry.name
 | 
			
		||||
                if (matchNamePrefix.isNotEmpty()) {
 | 
			
		||||
                    if (!entry.name.startsWith(matchNamePrefix)) {
 | 
			
		||||
                        zipIn.closeEntry()
 | 
			
		||||
                        entry = zipIn.nextEntry
 | 
			
		||||
                        continue
 | 
			
		||||
                    }
 | 
			
		||||
                    replaceMatchNamePrefix?.let {
 | 
			
		||||
                        writeEntryName = replaceMatchNamePrefix + writeEntryName.substring(
 | 
			
		||||
                            matchNamePrefix.length, writeEntryName.length
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                val filePath = destDir + File.separator + writeEntryName
 | 
			
		||||
                if (!entry.isDirectory) {
 | 
			
		||||
                    extractFile(zipIn, filePath, buffer)
 | 
			
		||||
                } else {
 | 
			
		||||
                    val dir = File(filePath)
 | 
			
		||||
                    dir.mkdirs()
 | 
			
		||||
                }
 | 
			
		||||
                zipIn.closeEntry()
 | 
			
		||||
                entry = zipIn.nextEntry
 | 
			
		||||
            }
 | 
			
		||||
            zipIn.close()
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            Log.e(TAG, "unzip error: $e")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun unzip(zipFile: String, destDir: String, matchNamePrefix: String = "") {
 | 
			
		||||
        return unzip(FileInputStream(zipFile), destDir, matchNamePrefix)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun extractFile(zipIn: ZipInputStream, filePath: String, buffer: ByteArray) {
 | 
			
		||||
        val fout = FileOutputStream(filePath)
 | 
			
		||||
        var length: Int
 | 
			
		||||
        while (zipIn.read(buffer).also { length = it } > 0) {
 | 
			
		||||
            fout.write(buffer, 0, length)
 | 
			
		||||
        }
 | 
			
		||||
        fout.close()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getZipResourcePath(zipFile: InputStream): String? {
 | 
			
		||||
        try {
 | 
			
		||||
            val zipIn = ZipInputStream(zipFile)
 | 
			
		||||
 | 
			
		||||
            var entry = zipIn.nextEntry
 | 
			
		||||
            while (entry != null) {
 | 
			
		||||
                if (entry.isDirectory) {
 | 
			
		||||
                    if (entry.name.endsWith("local-files/")) {
 | 
			
		||||
                        zipIn.close()
 | 
			
		||||
                        var retPath = File(entry.name, "..").canonicalPath
 | 
			
		||||
                        if (retPath.startsWith("/")) retPath = retPath.substring(1)
 | 
			
		||||
                        return retPath
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                zipIn.closeEntry()
 | 
			
		||||
                entry = zipIn.nextEntry
 | 
			
		||||
            }
 | 
			
		||||
            zipIn.close()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
            Log.e(TAG, "getZipResourcePath error: $e")
 | 
			
		||||
        }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getZipResourceVersion(zipFile: InputStream, basePath: String): String? {
 | 
			
		||||
        try {
 | 
			
		||||
            val targetVersionFilePath = File(basePath, "version.txt").canonicalPath
 | 
			
		||||
 | 
			
		||||
            val zipIn = ZipInputStream(zipFile)
 | 
			
		||||
            var entry = zipIn.nextEntry
 | 
			
		||||
            while (entry != null) {
 | 
			
		||||
                if (!entry.isDirectory) {
 | 
			
		||||
                    if ("/${entry.name}" == targetVersionFilePath) {
 | 
			
		||||
                        Log.d(TAG, "targetVersionFilePath: $targetVersionFilePath")
 | 
			
		||||
                        val reader = BufferedReader(InputStreamReader(zipIn))
 | 
			
		||||
                        val versionContent = reader.use { it.readText() }
 | 
			
		||||
                        Log.d(TAG, "versionContent: $versionContent")
 | 
			
		||||
                        zipIn.close()
 | 
			
		||||
                        return versionContent
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                zipIn.closeEntry()
 | 
			
		||||
                entry = zipIn.nextEntry
 | 
			
		||||
            }
 | 
			
		||||
            zipIn.close()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
            Log.e(TAG, "getZipResourceVersion error: $e")
 | 
			
		||||
        }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getZipResourceVersion(zipFile: String, basePath: String): String? {
 | 
			
		||||
        return getZipResourceVersion(FileInputStream(zipFile), basePath)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getZipResourceVersion(zipFile: String): String? {
 | 
			
		||||
        return try {
 | 
			
		||||
            val basePath = getZipResourcePath(FileInputStream(zipFile))
 | 
			
		||||
            basePath?.let { getZipResourceVersion(zipFile, it) }
 | 
			
		||||
        }
 | 
			
		||||
        catch (_: Exception) {
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateFilesFromZip(activity: Activity, zipFileUri: Uri, filesDir: File, deleteAfterUpdate: Boolean) {
 | 
			
		||||
        try {
 | 
			
		||||
            GakumasHookMain.showToast("Updating files from zip...")
 | 
			
		||||
 | 
			
		||||
            var basePath: String?
 | 
			
		||||
            activity.contentResolver.openInputStream(zipFileUri).use {
 | 
			
		||||
                basePath = it?.let { getZipResourcePath(it) }
 | 
			
		||||
                if (basePath == null) {
 | 
			
		||||
                    Log.e(TAG, "getZipResourcePath failed.")
 | 
			
		||||
                    return@updateFilesFromZip
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /*
 | 
			
		||||
            var resourceVersion: String?
 | 
			
		||||
            activity.contentResolver.openInputStream(zipFileUri).use {
 | 
			
		||||
                resourceVersion = it?.let { getZipResourceVersion(it, basePath!!) }
 | 
			
		||||
                Log.d(TAG, "resourceVersion: $resourceVersion ($basePath)")
 | 
			
		||||
            }*/
 | 
			
		||||
 | 
			
		||||
            activity.contentResolver.openInputStream(zipFileUri).use {
 | 
			
		||||
                it?.let {
 | 
			
		||||
                    unzip(it, File(filesDir, FilesChecker.localizationFilesDir).absolutePath,
 | 
			
		||||
                        basePath!!, "../gakumas-local/")
 | 
			
		||||
                    if (deleteAfterUpdate) {
 | 
			
		||||
                        activity.contentResolver.delete(zipFileUri, null, null)
 | 
			
		||||
                    }
 | 
			
		||||
                    GakumasHookMain.showToast("Update success.")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: java.io.FileNotFoundException) {
 | 
			
		||||
            Log.i(TAG, "updateFilesFromZip - file not found: $e")
 | 
			
		||||
            GakumasHookMain.showToast("Update file not found.")
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
            Log.e(TAG, "updateFilesFromZip failed: $e")
 | 
			
		||||
            GakumasHookMain.showToast("Updating files failed: $e")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -16,12 +16,16 @@ object FilesChecker {
 | 
			
		|||
    var filesUpdated = false
 | 
			
		||||
 | 
			
		||||
    fun initAndCheck(fileDir: File, modulePath: String) {
 | 
			
		||||
        this.filesDir = fileDir
 | 
			
		||||
        this.modulePath = modulePath
 | 
			
		||||
        initDir(fileDir, modulePath)
 | 
			
		||||
 | 
			
		||||
        checkFiles()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun initDir(fileDir: File, modulePath: String) {
 | 
			
		||||
        this.filesDir = fileDir
 | 
			
		||||
        this.modulePath = modulePath
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun checkFiles() {
 | 
			
		||||
        val installedVersion = getInstalledVersion()
 | 
			
		||||
        val pluginVersion = getPluginVersion()
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +84,7 @@ object FilesChecker {
 | 
			
		|||
        for (i in assets.list(localizationFilesDir)!!) {
 | 
			
		||||
            if (i.toString() == "version.txt") {
 | 
			
		||||
                val stream = assets.open("$localizationFilesDir/$i")
 | 
			
		||||
                return convertToString(stream)
 | 
			
		||||
                return convertToString(stream).trim()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return "0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +96,7 @@ object FilesChecker {
 | 
			
		|||
 | 
			
		||||
        val versionFile = File(pluginFilesDir, "version.txt")
 | 
			
		||||
        if (!versionFile.exists()) return "0.0"
 | 
			
		||||
        return versionFile.readText()
 | 
			
		||||
        return versionFile.readText().trim()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun convertToString(inputStream: InputStream?): String {
 | 
			
		||||
| 
						 | 
				
			
			@ -118,4 +122,49 @@ object FilesChecker {
 | 
			
		|||
        return stringBuilder.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun deleteRecursively(file: File): Boolean {
 | 
			
		||||
        if (file.isDirectory) {
 | 
			
		||||
            val children = file.listFiles()
 | 
			
		||||
            if (children != null) {
 | 
			
		||||
                for (child in children) {
 | 
			
		||||
                    val success = deleteRecursively(child)
 | 
			
		||||
                    if (!success) {
 | 
			
		||||
                        return false
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return file.delete()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun cleanAssets() {
 | 
			
		||||
        val pluginBasePath = File(filesDir, localizationFilesDir)
 | 
			
		||||
        val localFilesDir = File(pluginBasePath, "local-files")
 | 
			
		||||
 | 
			
		||||
        val fontFile = File(localFilesDir, "gkamsZHFontMIX.otf")
 | 
			
		||||
        val resourceDir = File(localFilesDir, "resource")
 | 
			
		||||
        val genericTransDir = File(localFilesDir, "genericTrans")
 | 
			
		||||
        val genericTransFile = File(localFilesDir, "generic.json")
 | 
			
		||||
        val i18nFile = File(localFilesDir, "localization.json")
 | 
			
		||||
        val masterTransDir = File(localFilesDir, "masterTrans")
 | 
			
		||||
 | 
			
		||||
        if (fontFile.exists()) {
 | 
			
		||||
            fontFile.delete()
 | 
			
		||||
        }
 | 
			
		||||
        if (deleteRecursively(resourceDir)) {
 | 
			
		||||
            resourceDir.mkdirs()
 | 
			
		||||
        }
 | 
			
		||||
        if (deleteRecursively(genericTransDir)) {
 | 
			
		||||
            genericTransDir.mkdirs()
 | 
			
		||||
        }
 | 
			
		||||
        if (deleteRecursively(masterTransDir)) {
 | 
			
		||||
            masterTransDir.mkdirs()
 | 
			
		||||
        }
 | 
			
		||||
        if (genericTransFile.exists()) {
 | 
			
		||||
            genericTransFile.writeText("{}")
 | 
			
		||||
        }
 | 
			
		||||
        if (i18nFile.exists()) {
 | 
			
		||||
            i18nFile.writeText("{}")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +1,10 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.hookUtils
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
 | 
			
		||||
object MainKeyEventDispatcher {
 | 
			
		||||
    private val targetDbgKeyList: IntArray = intArrayOf(19, 19, 20, 20, 21, 22, 21, 22, 30, 29)
 | 
			
		||||
    private var currentIndex = 0;
 | 
			
		||||
    private var currentIndex = 0
 | 
			
		||||
 | 
			
		||||
    fun checkDbgKey(code: Int, action: Int): Boolean {
 | 
			
		||||
        if (action == KeyEvent.ACTION_UP) return false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,145 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.mainUtils
 | 
			
		||||
 | 
			
		||||
import okhttp3.*
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.io.ByteArrayOutputStream
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
object FileDownloader {
 | 
			
		||||
    private val client = OkHttpClient.Builder()
 | 
			
		||||
        .connectTimeout(30, TimeUnit.SECONDS)
 | 
			
		||||
        .writeTimeout(0, TimeUnit.SECONDS)
 | 
			
		||||
        .readTimeout(0, TimeUnit.SECONDS)
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    private var call: Call? = null
 | 
			
		||||
 | 
			
		||||
    fun requestGet(request: Request, callback: Callback) {
 | 
			
		||||
        val client = OkHttpClient.Builder()
 | 
			
		||||
            .connectTimeout(30, TimeUnit.SECONDS)
 | 
			
		||||
            .build()
 | 
			
		||||
        val call = client.newCall(request)
 | 
			
		||||
        call.enqueue(callback)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun downloadFile(
 | 
			
		||||
        url: String,
 | 
			
		||||
        onDownload: (Float, downloaded: Long, size: Long) -> Unit,
 | 
			
		||||
        onSuccess: (ByteArray) -> Unit,
 | 
			
		||||
        onFailed: (Int, String) -> Unit,
 | 
			
		||||
        checkContentTypes: List<String>? = null
 | 
			
		||||
    ) {
 | 
			
		||||
        try {
 | 
			
		||||
            if (call != null) {
 | 
			
		||||
                onFailed(-1, "Another file is downloading.")
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            val request = Request.Builder()
 | 
			
		||||
                .url(url)
 | 
			
		||||
                .build()
 | 
			
		||||
 | 
			
		||||
            call = client.newCall(request)
 | 
			
		||||
            call?.enqueue(object : Callback {
 | 
			
		||||
                override fun onFailure(call: Call, e: IOException) {
 | 
			
		||||
                    this@FileDownloader.call = null
 | 
			
		||||
                    if (call.isCanceled()) {
 | 
			
		||||
                        onFailed(-1, "Download canceled")
 | 
			
		||||
                    } else {
 | 
			
		||||
                        onFailed(-1, e.message ?: "Unknown error")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onResponse(call: Call, response: Response) {
 | 
			
		||||
                    if (!response.isSuccessful) {
 | 
			
		||||
                        this@FileDownloader.call = null
 | 
			
		||||
                        onFailed(response.code, response.message)
 | 
			
		||||
                        return
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (checkContentTypes != null) {
 | 
			
		||||
                        val contentType = response.header("Content-Type")
 | 
			
		||||
                        if (!checkContentTypes.contains(contentType)) {
 | 
			
		||||
                            onFailed(-1, "Unexpected content type: $contentType")
 | 
			
		||||
                            this@FileDownloader.call = null
 | 
			
		||||
                            return
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    response.body?.let { responseBody ->
 | 
			
		||||
                        val contentLength = responseBody.contentLength()
 | 
			
		||||
                        val inputStream = responseBody.byteStream()
 | 
			
		||||
                        val buffer = ByteArray(8 * 1024)
 | 
			
		||||
                        var downloadedBytes = 0L
 | 
			
		||||
                        var read: Int
 | 
			
		||||
                        val outputStream = ByteArrayOutputStream()
 | 
			
		||||
 | 
			
		||||
                        try {
 | 
			
		||||
                            while (inputStream.read(buffer).also { read = it } != -1) {
 | 
			
		||||
                                outputStream.write(buffer, 0, read)
 | 
			
		||||
                                downloadedBytes += read
 | 
			
		||||
                                val progress = if (contentLength < 0) {
 | 
			
		||||
                                    0f
 | 
			
		||||
                                }
 | 
			
		||||
                                else {
 | 
			
		||||
                                    downloadedBytes.toFloat() / contentLength
 | 
			
		||||
                                }
 | 
			
		||||
                                onDownload(progress, downloadedBytes, contentLength)
 | 
			
		||||
                            }
 | 
			
		||||
                            onSuccess(outputStream.toByteArray())
 | 
			
		||||
                        } catch (e: IOException) {
 | 
			
		||||
                            if (call.isCanceled()) {
 | 
			
		||||
                                onFailed(-1, "Download canceled")
 | 
			
		||||
                            } else {
 | 
			
		||||
                                onFailed(-1, e.message ?: "Error reading stream")
 | 
			
		||||
                            }
 | 
			
		||||
                        } finally {
 | 
			
		||||
                            this@FileDownloader.call = null
 | 
			
		||||
                            inputStream.close()
 | 
			
		||||
                            outputStream.close()
 | 
			
		||||
                        }
 | 
			
		||||
                    } ?: run {
 | 
			
		||||
                        this@FileDownloader.call = null
 | 
			
		||||
                        onFailed(-1, "Response body is null")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
            onFailed(-1, e.toString())
 | 
			
		||||
            call = null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun cancel() {
 | 
			
		||||
        call?.cancel()
 | 
			
		||||
        this@FileDownloader.call = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * return: Status, newString
 | 
			
		||||
    * Status: 0 - not change, 1 - need check, 2 - modified, 3 - checked
 | 
			
		||||
    **/
 | 
			
		||||
    fun checkAndChangeDownloadURL(url: String, forceEdit: Boolean = false): Pair<Int, String> {
 | 
			
		||||
 | 
			
		||||
        if (!url.startsWith("https://github.com/")) {  // check github only
 | 
			
		||||
            return Pair(0, url)
 | 
			
		||||
        }
 | 
			
		||||
        if (url.endsWith(".zip")) {
 | 
			
		||||
            return Pair(0, url)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // https://github.com/chinosk6/GakumasTranslationData
 | 
			
		||||
        // https://github.com/chinosk6/GakumasTranslationData.git
 | 
			
		||||
        // https://github.com/chinosk6/GakumasTranslationData/archive/refs/heads/main.zip
 | 
			
		||||
        if (url.endsWith(".git")) {
 | 
			
		||||
            return Pair(2, "${url.substring(0, url.length - 4)}/archive/refs/heads/main.zip")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (forceEdit) {
 | 
			
		||||
            return Pair(3, "$url/archive/refs/heads/main.zip")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Pair(1, url)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.mainUtils
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
 | 
			
		||||
val json = Json {
 | 
			
		||||
    encodeDefaults = true
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.mainUtils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import net.lingala.zip4j.ZipFile
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.FileOutputStream
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
object LSPatchUtils {
 | 
			
		||||
    fun deleteDirectory(directoryToBeDeleted: File): Boolean {
 | 
			
		||||
        if (directoryToBeDeleted.isDirectory) {
 | 
			
		||||
            val children = directoryToBeDeleted.listFiles()
 | 
			
		||||
            if (children != null) {
 | 
			
		||||
                for (child in children) {
 | 
			
		||||
                    deleteDirectory(child)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return directoryToBeDeleted.delete()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun unzipXAPK(inputStream: InputStream, destDir: String) {
 | 
			
		||||
        val destDirFile = File(destDir)
 | 
			
		||||
        if (!destDirFile.exists()) {
 | 
			
		||||
            destDirFile.mkdirs()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val tempFile = File.createTempFile("xapk_temp", ".zip", destDirFile)
 | 
			
		||||
        tempFile.deleteOnExit()
 | 
			
		||||
 | 
			
		||||
        inputStream.use { input ->
 | 
			
		||||
            FileOutputStream(tempFile).use { output ->
 | 
			
		||||
                input.copyTo(output)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ZipFile(tempFile).extractAll(destDir)
 | 
			
		||||
        tempFile.delete()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun unzipXAPKWithProgress(inputStream: InputStream, destDir: String, progressCallback: (Int) -> Unit) {
 | 
			
		||||
        val destDirFile = File(destDir)
 | 
			
		||||
        if (!destDirFile.exists()) {
 | 
			
		||||
            destDirFile.mkdirs()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val tempFile = File.createTempFile("xapk_temp", ".zip", destDirFile)
 | 
			
		||||
        tempFile.deleteOnExit()
 | 
			
		||||
 | 
			
		||||
        inputStream.use { input ->
 | 
			
		||||
            FileOutputStream(tempFile).use { output ->
 | 
			
		||||
                input.copyTo(output)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val zipFile = ZipFile(tempFile)
 | 
			
		||||
        val progressMonitor = zipFile.progressMonitor
 | 
			
		||||
        val extractionThread = Thread {
 | 
			
		||||
            zipFile.extractAll(destDir)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        extractionThread.start()
 | 
			
		||||
 | 
			
		||||
        while (extractionThread.isAlive) {
 | 
			
		||||
            progressCallback(progressMonitor.percentDone)
 | 
			
		||||
            Thread.sleep(100)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        progressCallback(100)
 | 
			
		||||
        tempFile.delete()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,144 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.mainUtils
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import io.github.chinosk.gakumas.localify.TAG
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GithubReleaseModel
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import okhttp3.Call
 | 
			
		||||
import okhttp3.Callback
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
object RemoteAPIFilesChecker {
 | 
			
		||||
    const val BASEPATH = "remote_files"
 | 
			
		||||
 | 
			
		||||
    fun getLocalVersion(context: Context): String? {
 | 
			
		||||
        val basePath = File(context.filesDir, BASEPATH)
 | 
			
		||||
        val versionFile = File(basePath, "version.txt")
 | 
			
		||||
        if (!versionFile.exists()) {
 | 
			
		||||
            return null
 | 
			
		||||
        }
 | 
			
		||||
        return versionFile.readText()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // version.txt in zip file should be same with version parameter
 | 
			
		||||
    fun saveDownloadData(context: Context, data: ByteArray, version: String): File {
 | 
			
		||||
        val basePath = File(context.filesDir, BASEPATH)
 | 
			
		||||
        if (!basePath.exists()) {
 | 
			
		||||
            basePath.mkdirs()
 | 
			
		||||
        }
 | 
			
		||||
        val versionFile = File(basePath, "version.txt")
 | 
			
		||||
        val dataFile = File(basePath, "remote.zip")
 | 
			
		||||
        dataFile.writeBytes(data)
 | 
			
		||||
        versionFile.writeText(version)
 | 
			
		||||
        return dataFile
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun checkUpdateLocalAssets(context: Context, apiURL: String,
 | 
			
		||||
                               onFailed: (Int, String) -> Unit,
 | 
			
		||||
                               onResult: (data: GithubReleaseModel, localVersion: String?) -> Unit) {
 | 
			
		||||
        runCatching {
 | 
			
		||||
            val request = Request.Builder()
 | 
			
		||||
                .url(apiURL)
 | 
			
		||||
                .build()
 | 
			
		||||
            FileDownloader.requestGet(request, object : Callback {
 | 
			
		||||
                override fun onFailure(call: Call, e: IOException) {
 | 
			
		||||
                    onFailed(-1, e.toString())
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onResponse(call: Call, response: Response) {
 | 
			
		||||
                    runCatching {
 | 
			
		||||
                        response.use {
 | 
			
		||||
                            if (!response.isSuccessful) throw IOException("Unexpected code $response")
 | 
			
		||||
 | 
			
		||||
                            val responseBody = response.body?.string()
 | 
			
		||||
                            if (responseBody != null) {
 | 
			
		||||
                                val json = Json { ignoreUnknownKeys = true }
 | 
			
		||||
                                val releaseData = json.decodeFromString<GithubReleaseModel>(responseBody)
 | 
			
		||||
 | 
			
		||||
                                // Check update
 | 
			
		||||
                                // val releaseVersion = releaseData.tag_name
 | 
			
		||||
                                val localVersion = getLocalVersion(context)
 | 
			
		||||
                                // if (releaseVersion != localVersion) {
 | 
			
		||||
                                onResult(releaseData, localVersion)
 | 
			
		||||
                                // }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                onFailed(-1, "Response body is null")
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }.onFailure { e ->
 | 
			
		||||
                        Log.e(TAG, "checkUpdateLocalAssets failed", e)
 | 
			
		||||
                        onFailed(-1, e.toString())
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }.onFailure { e ->
 | 
			
		||||
            Log.e(TAG, "checkUpdateLocalAssets failed", e)
 | 
			
		||||
            onFailed(-1, e.toString())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateLocalAssets(context: Context, apiURL: String,
 | 
			
		||||
                          onDownload: (Float, downloaded: Long, size: Long) -> Unit,
 | 
			
		||||
                          onFailed: (Int, String) -> Unit,
 | 
			
		||||
                          onSuccess: (File, String) -> Unit) {
 | 
			
		||||
        runCatching {
 | 
			
		||||
            val request = Request.Builder()
 | 
			
		||||
                .url(apiURL)
 | 
			
		||||
                .build()
 | 
			
		||||
            FileDownloader.requestGet(request, object : Callback {
 | 
			
		||||
                override fun onFailure(call: Call, e: IOException) {
 | 
			
		||||
                    onFailed(-1, e.toString())
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onResponse(call: Call, response: Response) {
 | 
			
		||||
                    runCatching {
 | 
			
		||||
                        response.use {
 | 
			
		||||
                            if (!response.isSuccessful) throw IOException("Unexpected code $response")
 | 
			
		||||
 | 
			
		||||
                            val responseBody = response.body?.string()
 | 
			
		||||
                            if (responseBody != null) {
 | 
			
		||||
                                val json = Json { ignoreUnknownKeys = true }
 | 
			
		||||
                                val releaseData = json.decodeFromString<GithubReleaseModel>(responseBody)
 | 
			
		||||
 | 
			
		||||
                                // Check and save update
 | 
			
		||||
                                val releaseVersion = releaseData.tag_name
 | 
			
		||||
                                val localVersion = getLocalVersion(context)
 | 
			
		||||
                                if (releaseVersion != localVersion) {
 | 
			
		||||
                                    for (asset in releaseData.assets) {
 | 
			
		||||
                                        if (!asset.name.endsWith(".zip")) continue
 | 
			
		||||
                                        FileDownloader.downloadFile(asset.browser_download_url,
 | 
			
		||||
                                            onDownload, {data ->
 | 
			
		||||
                                                runCatching {
 | 
			
		||||
                                                    val saveFile = saveDownloadData(context, data, releaseVersion)
 | 
			
		||||
                                                    onSuccess(saveFile, releaseVersion)
 | 
			
		||||
                                                }.onFailure { e ->
 | 
			
		||||
                                                    onFailed(-1, e.toString())
 | 
			
		||||
                                                }
 | 
			
		||||
                                            },
 | 
			
		||||
                                            onFailed)
 | 
			
		||||
                                        break
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                onFailed(-1, "Response body is null")
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }.onFailure { e ->
 | 
			
		||||
                        Log.e(TAG, "updateLocalAssets failed", e)
 | 
			
		||||
                        onFailed(-1, e.toString())
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }.onFailure { e ->
 | 
			
		||||
            Log.e(TAG, "updateLocalAssets failed", e)
 | 
			
		||||
            onFailed(-1, e.toString())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.mainUtils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import android.content.IntentSender
 | 
			
		||||
import android.content.pm.IPackageInstaller
 | 
			
		||||
import android.content.pm.IPackageInstallerSession
 | 
			
		||||
import android.content.pm.IPackageManager
 | 
			
		||||
import android.content.pm.PackageInstaller
 | 
			
		||||
import android.content.pm.PackageInstallerHidden
 | 
			
		||||
import android.content.pm.PackageManager
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.os.IBinder
 | 
			
		||||
import android.os.IInterface
 | 
			
		||||
import android.os.Process
 | 
			
		||||
import android.os.SystemProperties
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import dev.rikka.tools.refine.Refine
 | 
			
		||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
 | 
			
		||||
import rikka.shizuku.Shizuku
 | 
			
		||||
import rikka.shizuku.ShizukuBinderWrapper
 | 
			
		||||
import rikka.shizuku.SystemServiceHelper
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// From https://github.com/LSPosed/LSPatch/blob/master/manager/src/main/java/org/lsposed/lspatch/util/ShizukuApi.kt
 | 
			
		||||
object ShizukuApi {
 | 
			
		||||
    private fun IBinder.wrap() = ShizukuBinderWrapper(this)
 | 
			
		||||
    private fun IInterface.asShizukuBinder() = this.asBinder().wrap()
 | 
			
		||||
 | 
			
		||||
    private val iPackageManager: IPackageManager by lazy {
 | 
			
		||||
        IPackageManager.Stub.asInterface(SystemServiceHelper.getSystemService("package").wrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val iPackageInstaller: IPackageInstaller by lazy {
 | 
			
		||||
        IPackageInstaller.Stub.asInterface(iPackageManager.packageInstaller.asShizukuBinder())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val packageInstaller: PackageInstaller by lazy {
 | 
			
		||||
        val userId = Process.myUserHandle().hashCode()
 | 
			
		||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
 | 
			
		||||
            Refine.unsafeCast(PackageInstallerHidden(iPackageInstaller, "com.android.shell", null, userId))
 | 
			
		||||
        } else {
 | 
			
		||||
            Refine.unsafeCast(PackageInstallerHidden(iPackageInstaller, "com.android.shell", userId))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var isBinderAvailable = false
 | 
			
		||||
    var isPermissionGranted by mutableStateOf(false)
 | 
			
		||||
 | 
			
		||||
    fun init() {
 | 
			
		||||
        HiddenApiBypass.addHiddenApiExemptions("")
 | 
			
		||||
        HiddenApiBypass.addHiddenApiExemptions("Landroid/content", "Landroid/os")
 | 
			
		||||
        Shizuku.addBinderReceivedListenerSticky {
 | 
			
		||||
            isBinderAvailable = true
 | 
			
		||||
            isPermissionGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
 | 
			
		||||
        }
 | 
			
		||||
        Shizuku.addBinderDeadListener {
 | 
			
		||||
            isBinderAvailable = false
 | 
			
		||||
            isPermissionGranted = false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun createPackageInstallerSession(params: PackageInstaller.SessionParams): PackageInstaller.Session {
 | 
			
		||||
        val sessionId = packageInstaller.createSession(params)
 | 
			
		||||
        val iSession = IPackageInstallerSession.Stub.asInterface(iPackageInstaller.openSession(sessionId).asShizukuBinder())
 | 
			
		||||
        return Refine.unsafeCast(PackageInstallerHidden.SessionHidden(iSession))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun isPackageInstalledWithoutPatch(packageName: String): Boolean {
 | 
			
		||||
        val userId = Process.myUserHandle().hashCode()
 | 
			
		||||
        val app = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
 | 
			
		||||
            iPackageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA.toLong(), userId)
 | 
			
		||||
        } else {
 | 
			
		||||
            iPackageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId)
 | 
			
		||||
        }
 | 
			
		||||
        return (app != null) && (app.metaData?.containsKey("lspatch") != true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun uninstallPackage(packageName: String, intentSender: IntentSender) {
 | 
			
		||||
        // packageInstaller.uninstall(packageName, intentSender)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun performDexOptMode(packageName: String): Boolean {
 | 
			
		||||
        return iPackageManager.performDexOptMode(
 | 
			
		||||
            packageName,
 | 
			
		||||
            SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false),
 | 
			
		||||
            "verify", true, true, null
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.mainUtils
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import io.github.chinosk.gakumas.localify.TAG
 | 
			
		||||
import rikka.shizuku.Shizuku
 | 
			
		||||
import rikka.shizuku.ShizukuRemoteProcess
 | 
			
		||||
import java.io.BufferedReader
 | 
			
		||||
import java.io.InputStreamReader
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
val shellTag = "${TAG}_Shell"
 | 
			
		||||
 | 
			
		||||
interface IOnShell {
 | 
			
		||||
    fun onShellLine(msg: String)
 | 
			
		||||
    fun onShellError(msg: String)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Created by sunilpaulmathew <sunil.kde@gmail.com> on November 12, 2022
 | 
			
		||||
*/
 | 
			
		||||
class ShizukuShell(private var mOutput: MutableList<String>, private var mCommand: String,
 | 
			
		||||
    private val shellCallback: IOnShell? = null) {
 | 
			
		||||
    val isBusy: Boolean
 | 
			
		||||
        get() = mOutput.size > 0 && mOutput[mOutput.size - 1] != "aShell: Finish"
 | 
			
		||||
 | 
			
		||||
    fun exec(): ShizukuShell {
 | 
			
		||||
        try {
 | 
			
		||||
            Log.i(shellTag, "Execute: $mCommand")
 | 
			
		||||
            shellCallback?.onShellLine(mCommand)
 | 
			
		||||
            mProcess = Shizuku.newProcess(arrayOf("sh", "-c", mCommand), null, mDir)
 | 
			
		||||
            val mInput = BufferedReader(InputStreamReader(mProcess!!.getInputStream()))
 | 
			
		||||
            val mError = BufferedReader(InputStreamReader(mProcess!!.getErrorStream()))
 | 
			
		||||
            var line: String
 | 
			
		||||
            while ((mInput.readLine().also { line = it }) != null) {
 | 
			
		||||
                Log.i(shellTag, line)
 | 
			
		||||
                shellCallback?.onShellLine(line)
 | 
			
		||||
                mOutput.add(line)
 | 
			
		||||
            }
 | 
			
		||||
            while ((mError.readLine().also { line = it }) != null) {
 | 
			
		||||
                Log.e(shellTag, line)
 | 
			
		||||
                shellCallback?.onShellError(line)
 | 
			
		||||
                mOutput.add("<font color=#FF0000>$line</font>")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle current directory
 | 
			
		||||
            if (mCommand.startsWith("cd ") && !mOutput[mOutput.size - 1]
 | 
			
		||||
                .endsWith("</font>")
 | 
			
		||||
            ) {
 | 
			
		||||
                val array: Array<String> =
 | 
			
		||||
                    mCommand.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
 | 
			
		||||
                        .toTypedArray()
 | 
			
		||||
                var dir: String
 | 
			
		||||
                dir = if (array[array.size - 1] == "/") {
 | 
			
		||||
                    "/"
 | 
			
		||||
                } else if (array[array.size - 1].startsWith("/")) {
 | 
			
		||||
                    array[array.size - 1]
 | 
			
		||||
                } else {
 | 
			
		||||
                    mDir + array[array.size - 1]
 | 
			
		||||
                }
 | 
			
		||||
                if (!dir.endsWith("/")) {
 | 
			
		||||
                    dir = "$dir/"
 | 
			
		||||
                }
 | 
			
		||||
                mDir = dir
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            mProcess!!.waitFor()
 | 
			
		||||
        } catch (ignored: Exception) {
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun destroy() {
 | 
			
		||||
        if (mProcess != null) mProcess!!.destroy()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private var mProcess: ShizukuRemoteProcess? = null
 | 
			
		||||
        private var mDir = "/"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.mainUtils
 | 
			
		||||
 | 
			
		||||
import java.time.ZonedDateTime
 | 
			
		||||
import java.time.format.DateTimeFormatter
 | 
			
		||||
import java.time.ZoneId
 | 
			
		||||
 | 
			
		||||
object TimeUtils {
 | 
			
		||||
    fun convertIsoToLocalTime(isoTimeString: String): String {
 | 
			
		||||
        val zonedDateTime = ZonedDateTime.parse(isoTimeString, DateTimeFormatter.ISO_DATE_TIME)
 | 
			
		||||
        val currentZoneId = ZoneId.systemDefault()
 | 
			
		||||
        val localZonedDateTime = zonedDateTime.withZoneSameInstant(currentZoneId)
 | 
			
		||||
        val outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
 | 
			
		||||
        return localZonedDateTime.format(outputFormatter)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.models
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class AboutPageConfig(
 | 
			
		||||
    val plugin_repo: String = "https://github.com/chinosk6/gakuen-imas-localify",
 | 
			
		||||
    val main_contributors: List<MainContributors> = listOf(),
 | 
			
		||||
    val contrib_img: ContribImg = ContribImg(
 | 
			
		||||
        "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
 | 
			
		||||
        "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class MainContributors(
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val links: List<Links>
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class ContribImg(
 | 
			
		||||
    val plugin: String,
 | 
			
		||||
    val translation: String,
 | 
			
		||||
    val translations: List<String> = listOf()
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Links(
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val link: String
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,21 +1,28 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.models
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class GakumasConfig (
 | 
			
		||||
    var dbgMode: Boolean = false,
 | 
			
		||||
    var enabled: Boolean = true,
 | 
			
		||||
    var lazyInit: Boolean = true,
 | 
			
		||||
    var replaceFont: Boolean = true,
 | 
			
		||||
    var textTest: Boolean = false,
 | 
			
		||||
    var useMasterTrans: Boolean = true,
 | 
			
		||||
    var dumpText: Boolean = false,
 | 
			
		||||
    var gameOrientation: Int = 0,
 | 
			
		||||
    var forceExportResource: Boolean = false,
 | 
			
		||||
    var enableFreeCamera: Boolean = false,
 | 
			
		||||
    var targetFrameRate: Int = 0,
 | 
			
		||||
    var unlockAllLive: Boolean = false,
 | 
			
		||||
    var unlockAllLiveCostume: Boolean = false,
 | 
			
		||||
    var enableLiveCustomeDress: Boolean = false,
 | 
			
		||||
    var liveCustomeHeadId: String = "",
 | 
			
		||||
    var liveCustomeCostumeId: String = "",
 | 
			
		||||
 | 
			
		||||
    var loginAsIOS: Boolean = false,
 | 
			
		||||
 | 
			
		||||
    var useCustomeGraphicSettings: Boolean = false,
 | 
			
		||||
    var renderScale: Float = 0.77f,
 | 
			
		||||
    var qualitySettingsLevel: Int = 3,
 | 
			
		||||
| 
						 | 
				
			
			@ -42,4 +49,6 @@ data class GakumasConfig (
 | 
			
		|||
    var bLimitYy: Float = 1.0f,
 | 
			
		||||
    var bLimitZx: Float = 1.0f,
 | 
			
		||||
    var bLimitZy: Float = 1.0f,
 | 
			
		||||
 | 
			
		||||
    var pf: Boolean = false
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.models
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class User(
 | 
			
		||||
    val avatar_url: String,
 | 
			
		||||
    val events_url: String,
 | 
			
		||||
    val followers_url: String,
 | 
			
		||||
    val following_url: String,
 | 
			
		||||
    val gists_url: String,
 | 
			
		||||
    val gravatar_id: String?,
 | 
			
		||||
    val html_url: String,
 | 
			
		||||
    val id: Int,
 | 
			
		||||
    val login: String,
 | 
			
		||||
    val node_id: String,
 | 
			
		||||
    val organizations_url: String,
 | 
			
		||||
    val received_events_url: String,
 | 
			
		||||
    val repos_url: String,
 | 
			
		||||
    val site_admin: Boolean,
 | 
			
		||||
    val starred_url: String,
 | 
			
		||||
    val subscriptions_url: String,
 | 
			
		||||
    val type: String,
 | 
			
		||||
    val url: String
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Asset(
 | 
			
		||||
    val browser_download_url: String,
 | 
			
		||||
//    val content_type: String,
 | 
			
		||||
//    val created_at: String,
 | 
			
		||||
//    val download_count: Int,
 | 
			
		||||
//    val id: Int,
 | 
			
		||||
//    val label: String?,
 | 
			
		||||
    val name: String,
 | 
			
		||||
//    val node_id: String,
 | 
			
		||||
//    val size: Int,
 | 
			
		||||
//    val state: String,
 | 
			
		||||
//    val updated_at: String,
 | 
			
		||||
//    val uploader: User,
 | 
			
		||||
//    val url: String
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class GithubReleaseModel(
 | 
			
		||||
    val assets: List<Asset>,
 | 
			
		||||
//    val assets_url: String,
 | 
			
		||||
//    val author: User,
 | 
			
		||||
    val body: String,
 | 
			
		||||
//    val created_at: String,
 | 
			
		||||
//    val draft: Boolean,
 | 
			
		||||
//    val html_url: String,
 | 
			
		||||
//    val id: Int,
 | 
			
		||||
    val name: String?,
 | 
			
		||||
//    val node_id: String,
 | 
			
		||||
//    val prerelease: Boolean,
 | 
			
		||||
    val published_at: String,
 | 
			
		||||
    val tag_name: String,
 | 
			
		||||
//    val tarball_url: String,
 | 
			
		||||
//    val target_commitish: String,
 | 
			
		||||
//    val upload_url: String,
 | 
			
		||||
//    val url: String,
 | 
			
		||||
//    val zipball_url: String
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.models
 | 
			
		||||
 | 
			
		||||
data class ProgressData(
 | 
			
		||||
    var current: Long = 0,
 | 
			
		||||
    var total: Long = 1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
object NativeInitProgress {
 | 
			
		||||
    var assembliesProgress = ProgressData()
 | 
			
		||||
    var classProgress = ProgressData()
 | 
			
		||||
    var startInit: Boolean = false
 | 
			
		||||
 | 
			
		||||
    fun setAssembliesProgressData(current: Long, total: Long) {
 | 
			
		||||
        assembliesProgress.current = current
 | 
			
		||||
        assembliesProgress.total = total
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setClassProgressData(current: Long, total: Long) {
 | 
			
		||||
        classProgress.current = current
 | 
			
		||||
        classProgress.total = total
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    external fun pluginInitProgressLooper(progress: NativeInitProgress)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.models
 | 
			
		||||
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.json
 | 
			
		||||
import kotlinx.serialization.KSerializer
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.descriptors.SerialDescriptor
 | 
			
		||||
import kotlinx.serialization.encoding.Decoder
 | 
			
		||||
import kotlinx.serialization.encoding.Encoder
 | 
			
		||||
import kotlinx.serialization.encoding.encodeStructure
 | 
			
		||||
import kotlinx.serialization.json.JsonElement
 | 
			
		||||
import kotlinx.serialization.json.encodeToJsonElement
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class ProgramConfig(
 | 
			
		||||
    var checkBuiltInAssets: Boolean = true,
 | 
			
		||||
    var transRemoteZipUrl: String = "",
 | 
			
		||||
    var useRemoteAssets: Boolean = false,
 | 
			
		||||
    var useAPIAssets: Boolean = false,
 | 
			
		||||
    var useAPIAssetsURL: String = "",
 | 
			
		||||
    var delRemoteAfterUpdate: Boolean = true,
 | 
			
		||||
    var cleanLocalAssets: Boolean = false,
 | 
			
		||||
 | 
			
		||||
    // var localAPIAssetsVersion: String = "",
 | 
			
		||||
    var p: Boolean = false
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class ProgramConfigSerializer(
 | 
			
		||||
    private val excludes: List<String> = emptyList(),
 | 
			
		||||
) : KSerializer<ProgramConfig> {
 | 
			
		||||
    override val descriptor: SerialDescriptor = ProgramConfig.serializer().descriptor
 | 
			
		||||
    override fun serialize(encoder: Encoder, value: ProgramConfig) {
 | 
			
		||||
        val jsonObject = json.encodeToJsonElement(value).jsonObject
 | 
			
		||||
        encoder.encodeStructure(descriptor) {
 | 
			
		||||
            jsonObject.keys.forEachIndexed { index, k ->
 | 
			
		||||
                if (k in excludes) return@forEachIndexed
 | 
			
		||||
                encodeSerializableElement(descriptor, index, JsonElement.serializer(), jsonObject[k]!!)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun deserialize(decoder: Decoder): ProgramConfig {
 | 
			
		||||
        return ProgramConfig.serializer().deserialize(decoder)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,86 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.models
 | 
			
		||||
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.lifecycle.ViewModelProvider
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.asStateFlow
 | 
			
		||||
 | 
			
		||||
open class CollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : ViewModel() {
 | 
			
		||||
    open var expanded by mutableStateOf(initiallyBreastExpanded)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class BreastCollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : CollapsibleBoxViewModel(initiallyBreastExpanded) {
 | 
			
		||||
    override var expanded by mutableStateOf(initiallyBreastExpanded)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ResourceCollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : CollapsibleBoxViewModel(initiallyBreastExpanded) {
 | 
			
		||||
    override var expanded by mutableStateOf(initiallyBreastExpanded)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class BreastCollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory {
 | 
			
		||||
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
 | 
			
		||||
        if (modelClass.isAssignableFrom(BreastCollapsibleBoxViewModel::class.java)) {
 | 
			
		||||
            @Suppress("UNCHECKED_CAST")
 | 
			
		||||
            return BreastCollapsibleBoxViewModel(initiallyExpanded) as T
 | 
			
		||||
        }
 | 
			
		||||
        throw IllegalArgumentException("Unknown ViewModel class")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ResourceCollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory {
 | 
			
		||||
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
 | 
			
		||||
        if (modelClass.isAssignableFrom(ResourceCollapsibleBoxViewModel::class.java)) {
 | 
			
		||||
            @Suppress("UNCHECKED_CAST")
 | 
			
		||||
            return ResourceCollapsibleBoxViewModel(initiallyExpanded) as T
 | 
			
		||||
        }
 | 
			
		||||
        throw IllegalArgumentException("Unknown ViewModel class")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProgramConfigViewModelFactory(private val initialValue: ProgramConfig,
 | 
			
		||||
                                    private val localResourceVersion: String) : ViewModelProvider.Factory {
 | 
			
		||||
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
 | 
			
		||||
        if (modelClass.isAssignableFrom(ProgramConfigViewModel::class.java)) {
 | 
			
		||||
            @Suppress("UNCHECKED_CAST")
 | 
			
		||||
            return ProgramConfigViewModel(initialValue, localResourceVersion) as T
 | 
			
		||||
        }
 | 
			
		||||
        throw IllegalArgumentException("Unknown ViewModel class")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data class ConfirmStateModel(
 | 
			
		||||
    var isShow: Boolean = false,
 | 
			
		||||
    var title: String = "GakuConfirm Title",
 | 
			
		||||
    var content: String = "GakuConfirm Content",
 | 
			
		||||
    var onConfirm: () -> Unit = {},
 | 
			
		||||
    var onCancel: () -> Unit = {},
 | 
			
		||||
    var p: Boolean = false
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class ProgramConfigViewModel(initValue: ProgramConfig, initLocalResourceVersion: String) : ViewModel() {
 | 
			
		||||
    val configState = MutableStateFlow(initValue)
 | 
			
		||||
    val config: StateFlow<ProgramConfig> = configState.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    val downloadProgressState = MutableStateFlow(-1f)
 | 
			
		||||
    val downloadProgress: StateFlow<Float> = downloadProgressState.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    val downloadAbleState = MutableStateFlow(true)
 | 
			
		||||
    val downloadAble: StateFlow<Boolean> = downloadAbleState.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    val localResourceVersionState = MutableStateFlow(initLocalResourceVersion)
 | 
			
		||||
    val localResourceVersion: StateFlow<String> = localResourceVersionState.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    val localAPIResourceVersionState = MutableStateFlow(initLocalResourceVersion)
 | 
			
		||||
    val localAPIResourceVersion: StateFlow<String> = localAPIResourceVersionState.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    val errorStringState = MutableStateFlow("")
 | 
			
		||||
    val errorString: StateFlow<String> = errorStringState.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    val mainUIConfirmState = MutableStateFlow(ConfirmStateModel())
 | 
			
		||||
    val mainUIConfirm: StateFlow<ConfirmStateModel> = mainUIConfirmState.asStateFlow()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.border
 | 
			
		||||
import androidx.compose.foundation.layout.PaddingValues
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.width
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.material3.Button
 | 
			
		||||
import androidx.compose.material3.ButtonDefaults
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.clip
 | 
			
		||||
import androidx.compose.ui.draw.shadow
 | 
			
		||||
import androidx.compose.ui.geometry.Offset
 | 
			
		||||
import androidx.compose.ui.graphics.Brush
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.graphics.Shape
 | 
			
		||||
import androidx.compose.ui.layout.onGloballyPositioned
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.IntSize
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuButton(
 | 
			
		||||
    onClick: () -> Unit,
 | 
			
		||||
    text: String,
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    shape: Shape = RoundedCornerShape(50.dp), // 用于实现左右两边的半圆角
 | 
			
		||||
    shadowElevation: Dp = 8.dp, // 阴影的高度
 | 
			
		||||
    borderWidth: Dp = 1.dp, // 描边的宽度
 | 
			
		||||
    borderColor: Color = Color.Transparent, // 描边的颜色
 | 
			
		||||
    enabled: Boolean = true,
 | 
			
		||||
    bgColors: List<Color>? = null,
 | 
			
		||||
    textColor: Color? = null
 | 
			
		||||
) {
 | 
			
		||||
    var buttonSize by remember { mutableStateOf(IntSize.Zero) }
 | 
			
		||||
 | 
			
		||||
    val gradient = remember(buttonSize) {
 | 
			
		||||
        Brush.linearGradient(
 | 
			
		||||
            colors = bgColors
 | 
			
		||||
                ?: if (enabled) listOf(Color(0xFFFF5F19), Color(0xFFFFA028)) else
 | 
			
		||||
                    listOf(Color(0xFFF9F9F9), Color(0xFFF0F0F0)),
 | 
			
		||||
            start = Offset(0f, 0f),
 | 
			
		||||
            end = Offset(buttonSize.width.toFloat(), buttonSize.height.toFloat()) // 动态终点
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Button(
 | 
			
		||||
        onClick = onClick,
 | 
			
		||||
        enabled = enabled,
 | 
			
		||||
        colors = ButtonDefaults.buttonColors(
 | 
			
		||||
            containerColor = Color.Transparent
 | 
			
		||||
        ),
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
            .onGloballyPositioned { layoutCoordinates ->
 | 
			
		||||
                buttonSize = layoutCoordinates.size
 | 
			
		||||
            }
 | 
			
		||||
            .shadow(elevation = shadowElevation, shape = shape)
 | 
			
		||||
            .clip(shape)
 | 
			
		||||
            .background(gradient)
 | 
			
		||||
            .border(borderWidth, borderColor, shape),
 | 
			
		||||
        contentPadding = PaddingValues(0.dp)
 | 
			
		||||
    ) {
 | 
			
		||||
        Text(text = text, color = textColor ?: if (enabled) Color.White else Color(0xFF111111))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuButtonPreview() {
 | 
			
		||||
    GakuButton(modifier = Modifier.width(80.dp).height(40.dp), text = "Button", onClick = {},
 | 
			
		||||
        enabled = true)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,238 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.animation.core.animateFloatAsState
 | 
			
		||||
import androidx.compose.animation.core.tween
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.layout.*
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.foundation.Image
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.material3.*
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableFloatStateOf
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.shadow
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.layout.ContentScale
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.IntOffset
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import io.github.chinosk.gakumas.localify.R
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlin.math.roundToInt
 | 
			
		||||
 | 
			
		||||
const val ANIMATION_TIME = 320
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun FullScreenBoxWithAnimation(
 | 
			
		||||
    isVisible: Boolean,
 | 
			
		||||
    onDismiss: () -> Unit,
 | 
			
		||||
    content: @Composable () -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    val animatedAlpha by animateFloatAsState(
 | 
			
		||||
        targetValue = if (isVisible) 0.6f else 0f, label = "animatedAlpha",
 | 
			
		||||
        animationSpec = tween(durationMillis = ANIMATION_TIME)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxSize()
 | 
			
		||||
            .background(Color.Black.copy(alpha = animatedAlpha))
 | 
			
		||||
            .clickable {
 | 
			
		||||
                // isVisible2 = false
 | 
			
		||||
                onDismiss()
 | 
			
		||||
            },
 | 
			
		||||
        contentAlignment = Alignment.BottomCenter
 | 
			
		||||
    ) {
 | 
			
		||||
        content()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuGroupConfirm(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    title: String = "Title",
 | 
			
		||||
    maxWidth: Dp = 400.dp,
 | 
			
		||||
    contentPadding: Dp = 8.dp,
 | 
			
		||||
    rightHead: @Composable (() -> Unit)? = null,
 | 
			
		||||
    onHeadClick: () -> Unit = {},
 | 
			
		||||
    onConfirm: () -> Unit = {},
 | 
			
		||||
    onCancel: () -> Unit = {},
 | 
			
		||||
    contentHeightForAnimation: Float = 400f,
 | 
			
		||||
    initIsVisible: Boolean = false,
 | 
			
		||||
    baseModifier: Modifier = Modifier,
 | 
			
		||||
    content: @Composable () -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    val scoop = rememberCoroutineScope()
 | 
			
		||||
    var isVisible by remember { mutableStateOf(initIsVisible) }
 | 
			
		||||
    val offsetY by animateFloatAsState(
 | 
			
		||||
        targetValue = if (isVisible) -35f else contentHeightForAnimation, // 控制Box移动的距离
 | 
			
		||||
        animationSpec = tween(durationMillis = ANIMATION_TIME), label = "offsetY"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    LaunchedEffect(Unit) {
 | 
			
		||||
        isVisible = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Scaffold(modifier = baseModifier.background(
 | 
			
		||||
        color = Color.Transparent
 | 
			
		||||
    ),
 | 
			
		||||
        containerColor = Color.Transparent) {
 | 
			
		||||
        FullScreenBoxWithAnimation(
 | 
			
		||||
            isVisible = isVisible,
 | 
			
		||||
            onDismiss = {
 | 
			
		||||
                isVisible = false
 | 
			
		||||
                scoop.launch {
 | 
			
		||||
                    delay(ANIMATION_TIME.toLong())
 | 
			
		||||
                    onCancel()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        ) {
 | 
			
		||||
            Box(Modifier
 | 
			
		||||
                .offset { IntOffset(0, offsetY.roundToInt()) }
 | 
			
		||||
                .widthIn(max = maxWidth)
 | 
			
		||||
                .clickable {  }) {
 | 
			
		||||
                Column(modifier = modifier.widthIn(max = maxWidth)) {
 | 
			
		||||
                    // Header
 | 
			
		||||
                    Box(
 | 
			
		||||
                        modifier = modifier
 | 
			
		||||
                            .fillMaxWidth()
 | 
			
		||||
                            .background(Color.Transparent)
 | 
			
		||||
                            .height(30.dp)
 | 
			
		||||
                            .clickable {
 | 
			
		||||
                                onHeadClick()
 | 
			
		||||
                            }
 | 
			
		||||
                    ) {
 | 
			
		||||
                        Image(
 | 
			
		||||
                            painter = painterResource(id = R.drawable.bg_sheet_title),
 | 
			
		||||
                            contentDescription = null,
 | 
			
		||||
                            // modifier = Modifier.fillMaxSize(),
 | 
			
		||||
                            modifier = Modifier.fillMaxSize(),
 | 
			
		||||
                            contentScale = ContentScale.FillBounds
 | 
			
		||||
                        )
 | 
			
		||||
                        Text(
 | 
			
		||||
                            text = title,
 | 
			
		||||
                            style = MaterialTheme.typography.titleSmall,
 | 
			
		||||
                            color = Color.White,
 | 
			
		||||
                            modifier = modifier
 | 
			
		||||
                                .align(Alignment.CenterStart)
 | 
			
		||||
                                .padding(start = (maxWidth.value * 0.043f).dp)
 | 
			
		||||
                        )
 | 
			
		||||
                        if (rightHead != null) {
 | 
			
		||||
                            Box(modifier = Modifier
 | 
			
		||||
                                .align(Alignment.CenterEnd)
 | 
			
		||||
                                .padding(end = (maxWidth.value * 0.1f).dp)) {
 | 
			
		||||
                                rightHead()
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Content
 | 
			
		||||
                    Row {
 | 
			
		||||
                        Spacer(modifier = Modifier.width(4.dp))
 | 
			
		||||
                        Box(
 | 
			
		||||
                            modifier = modifier
 | 
			
		||||
                                .shadow(
 | 
			
		||||
                                    4.dp, RoundedCornerShape(
 | 
			
		||||
                                        bottomStart = 16.dp,
 | 
			
		||||
                                        bottomEnd = 8.dp,
 | 
			
		||||
                                        topEnd = 0.dp,
 | 
			
		||||
                                        topStart = 0.dp
 | 
			
		||||
                                    )
 | 
			
		||||
                                )
 | 
			
		||||
                                .background(
 | 
			
		||||
                                    color = Color.White,
 | 
			
		||||
                                    shape = RoundedCornerShape(
 | 
			
		||||
                                        bottomStart = 16.dp,
 | 
			
		||||
                                        bottomEnd = 8.dp
 | 
			
		||||
                                    )
 | 
			
		||||
                                )
 | 
			
		||||
                                .padding(
 | 
			
		||||
                                    contentPadding + 5.dp, contentPadding, contentPadding,
 | 
			
		||||
                                    contentPadding
 | 
			
		||||
                                )
 | 
			
		||||
                                .fillMaxWidth()
 | 
			
		||||
                        ) {
 | 
			
		||||
                            Column {
 | 
			
		||||
                                content()
 | 
			
		||||
                                Spacer(modifier = Modifier.height(22.dp))
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Spacer(modifier = Modifier.height(22.dp))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Box(modifier = Modifier
 | 
			
		||||
                    .align(Alignment.BottomCenter)
 | 
			
		||||
                    .fillMaxWidth()) {
 | 
			
		||||
                    Row(Modifier.fillMaxWidth(),
 | 
			
		||||
                        horizontalArrangement = Arrangement.Center) {
 | 
			
		||||
                        Spacer(modifier = Modifier.sizeIn(minWidth = 32.dp))
 | 
			
		||||
                        Row(
 | 
			
		||||
                            Modifier
 | 
			
		||||
                                .fillMaxWidth()
 | 
			
		||||
                                .weight(1f),
 | 
			
		||||
                            horizontalArrangement = Arrangement.Center) {
 | 
			
		||||
                            GakuButton(modifier = Modifier
 | 
			
		||||
                                .height(40.dp)
 | 
			
		||||
                                .sizeIn(minWidth = 100.dp),
 | 
			
		||||
                                text = stringResource(R.string.cancel),
 | 
			
		||||
                                bgColors = listOf(Color(0xFFF9F9F9), Color(0xFFF0F0F0)),
 | 
			
		||||
                                textColor = Color(0xFF111111),
 | 
			
		||||
                                onClick = { scoop.launch {
 | 
			
		||||
                                    isVisible = false
 | 
			
		||||
                                    delay(ANIMATION_TIME.toLong())
 | 
			
		||||
                                    onCancel()
 | 
			
		||||
                                } })
 | 
			
		||||
                        }
 | 
			
		||||
                        Row(
 | 
			
		||||
                            Modifier
 | 
			
		||||
                                .fillMaxWidth()
 | 
			
		||||
                                .weight(1f),
 | 
			
		||||
                            horizontalArrangement = Arrangement.Center) {
 | 
			
		||||
                            GakuButton(modifier = Modifier
 | 
			
		||||
                                .height(40.dp)
 | 
			
		||||
                                .sizeIn(minWidth = 100.dp),
 | 
			
		||||
                                text = stringResource(R.string.ok),
 | 
			
		||||
                                onClick = { scoop.launch {
 | 
			
		||||
                                    isVisible = false
 | 
			
		||||
                                    delay(ANIMATION_TIME.toLong())
 | 
			
		||||
                                    onConfirm()
 | 
			
		||||
                                } })
 | 
			
		||||
                        }
 | 
			
		||||
                        Spacer(modifier = Modifier.sizeIn(minWidth = 32.dp))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun PreviewGakuGroupConfirm() {
 | 
			
		||||
    GakuGroupConfirm(
 | 
			
		||||
        title = "Confirm Title",
 | 
			
		||||
        initIsVisible = true
 | 
			
		||||
    ) {
 | 
			
		||||
        Column {
 | 
			
		||||
            Text("This is the content of the GakuGroupConfirm.")
 | 
			
		||||
            Text("This is the content of the GakuGroupConfirm.")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,110 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.layout.*
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.foundation.Image
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.material3.*
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.shadow
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.layout.ContentScale
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import io.github.chinosk.gakumas.localify.R
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuGroupBox(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    title: String = "Title",
 | 
			
		||||
    maxWidth: Dp = 500.dp,
 | 
			
		||||
    contentPadding: Dp = 8.dp,
 | 
			
		||||
    rightHead: @Composable (() -> Unit)? = null,
 | 
			
		||||
    onHeadClick: () -> Unit = {},
 | 
			
		||||
    content: @Composable () -> Unit
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .shadow(4.dp, RoundedCornerShape(
 | 
			
		||||
                bottomStart = 16.dp,
 | 
			
		||||
                bottomEnd = 8.dp,
 | 
			
		||||
                topEnd = 16.dp,
 | 
			
		||||
                topStart = 0.dp
 | 
			
		||||
            ))
 | 
			
		||||
            // .background(Color.White, RoundedCornerShape(8.dp))
 | 
			
		||||
    ) {
 | 
			
		||||
        Column(modifier = modifier.widthIn(max = maxWidth)) {
 | 
			
		||||
            // Header
 | 
			
		||||
            Box(
 | 
			
		||||
                modifier = modifier
 | 
			
		||||
                    .fillMaxWidth()
 | 
			
		||||
                    .background(Color.Transparent)
 | 
			
		||||
                    .height(23.dp)
 | 
			
		||||
                    .clickable {
 | 
			
		||||
                        onHeadClick()
 | 
			
		||||
                    }
 | 
			
		||||
            ) {
 | 
			
		||||
                Image(
 | 
			
		||||
                    painter = painterResource(id = R.drawable.bg_h1),
 | 
			
		||||
                    contentDescription = null,
 | 
			
		||||
                    // modifier = Modifier.fillMaxSize(),
 | 
			
		||||
                    modifier = Modifier.fillMaxSize(),
 | 
			
		||||
                    contentScale = ContentScale.FillBounds
 | 
			
		||||
                )
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = title,
 | 
			
		||||
                    style = MaterialTheme.typography.titleSmall,
 | 
			
		||||
                    color = Color.White,
 | 
			
		||||
                    modifier = modifier
 | 
			
		||||
                        .align(Alignment.CenterStart)
 | 
			
		||||
                        .padding(start = (maxWidth.value * 0.043f).dp)
 | 
			
		||||
                )
 | 
			
		||||
                if (rightHead != null) {
 | 
			
		||||
                    Box(modifier = Modifier
 | 
			
		||||
                        .align(Alignment.CenterEnd)
 | 
			
		||||
                        .padding(end = (maxWidth.value * 0.1f).dp)) {
 | 
			
		||||
                        rightHead()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Content
 | 
			
		||||
            Box(
 | 
			
		||||
                modifier = modifier
 | 
			
		||||
                    .background(
 | 
			
		||||
                        color = Color.White,
 | 
			
		||||
                        shape = RoundedCornerShape(
 | 
			
		||||
                            bottomStart = 16.dp,
 | 
			
		||||
                            bottomEnd = 8.dp
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                    .padding(contentPadding)
 | 
			
		||||
                    .fillMaxWidth()
 | 
			
		||||
            ) {
 | 
			
		||||
                content()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun PreviewGakuGroupBox() {
 | 
			
		||||
    GakuGroupBox(
 | 
			
		||||
        title = "GroupBox Title"
 | 
			
		||||
    ) {
 | 
			
		||||
        Column {
 | 
			
		||||
            Text("This is the content of the GroupBox.")
 | 
			
		||||
            Text("This is the content of the GroupBox.")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.animation.core.animateFloatAsState
 | 
			
		||||
import androidx.compose.foundation.layout.*
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.material3.*
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.clip
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuProgressBar(modifier: Modifier = Modifier, progress: Float, isError: Boolean = false) {
 | 
			
		||||
    val animatedProgress by animateFloatAsState(targetValue = progress, label = "progressAnime")
 | 
			
		||||
 | 
			
		||||
    Row(
 | 
			
		||||
        verticalAlignment = androidx.compose.ui.Alignment.CenterVertically,
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
    ) {
 | 
			
		||||
        if (progress <= 0f) {
 | 
			
		||||
            LinearProgressIndicator(
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .weight(1f)
 | 
			
		||||
                    .clip(RoundedCornerShape(4.dp))
 | 
			
		||||
                    .height(8.dp),
 | 
			
		||||
                color = if (isError) Color(0xFFE2041B) else Color(0xFFF9C114),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            LinearProgressIndicator(
 | 
			
		||||
                progress = { animatedProgress },
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .weight(1f)
 | 
			
		||||
                    .clip(RoundedCornerShape(4.dp))
 | 
			
		||||
                    .height(8.dp),
 | 
			
		||||
                color = if (isError) Color(0xFFE2041B) else Color(0xFFF9C114),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Spacer(modifier = Modifier.width(8.dp))
 | 
			
		||||
 | 
			
		||||
        Text(if (progress > 0f) "${(progress * 100).toInt()}%" else if (isError) "Failed" else "Downloading")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuProgressBarPreview() {
 | 
			
		||||
    GakuProgressBar(progress = 0.25f)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.gestures.detectTapGestures
 | 
			
		||||
import androidx.compose.foundation.layout.*
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.RadioButton
 | 
			
		||||
import androidx.compose.material3.RadioButtonDefaults
 | 
			
		||||
import androidx.compose.material3.Surface
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.input.pointer.pointerInput
 | 
			
		||||
import androidx.compose.ui.text.TextStyle
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.TextUnit
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.base.AutoSizeText
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuRadio(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    text: String,
 | 
			
		||||
    selected: Boolean,
 | 
			
		||||
    fontSize: TextUnit = 14.sp,
 | 
			
		||||
    onClick: () -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    val backgroundColor = if (selected) Color(0xFFFFEEC3) else Color(0xFFF8F7F5)
 | 
			
		||||
    val radioButtonColor = if (selected) Color(0xFFFF7601) else MaterialTheme.colorScheme.onSurface
 | 
			
		||||
 | 
			
		||||
    Surface(
 | 
			
		||||
        shape = RoundedCornerShape(
 | 
			
		||||
            topStart = 4.dp,
 | 
			
		||||
            topEnd = 16.dp,
 | 
			
		||||
            bottomEnd = 4.dp,
 | 
			
		||||
            bottomStart = 16.dp
 | 
			
		||||
        ),
 | 
			
		||||
        color = backgroundColor,
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
            .pointerInput(Unit) {
 | 
			
		||||
                detectTapGestures(onTap = {
 | 
			
		||||
                    onClick()
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
    ) {
 | 
			
		||||
        Row(
 | 
			
		||||
            verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
            modifier = modifier.padding(start = 0.dp, end = 4.dp)
 | 
			
		||||
        ) {
 | 
			
		||||
            RadioButton(
 | 
			
		||||
                modifier = Modifier.padding(start = 0.dp),
 | 
			
		||||
                selected = selected,
 | 
			
		||||
                onClick = onClick,
 | 
			
		||||
                colors = RadioButtonDefaults.colors(
 | 
			
		||||
                    selectedColor = radioButtonColor,
 | 
			
		||||
                    unselectedColor = MaterialTheme.colorScheme.onSurface
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            // Spacer(modifier = modifier.width(16.dp))
 | 
			
		||||
            AutoSizeText(text = text,
 | 
			
		||||
                textStyle = TextStyle(color = MaterialTheme.colorScheme.onSurface,
 | 
			
		||||
                    fontSize = fontSize))
 | 
			
		||||
            // Text(text = text, color = MaterialTheme.colorScheme.onSurface, fontSize = fontSize)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 100, heightDp = 40)
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuRadioPreview() {
 | 
			
		||||
    GakuRadio(text = "GakuRadioooo", selected = true, onClick = {})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.material3.Switch
 | 
			
		||||
import androidx.compose.material3.SwitchDefaults
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.base.AutoSizeText
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuSwitch(modifier: Modifier = Modifier,
 | 
			
		||||
               text: String = "",
 | 
			
		||||
               checked: Boolean = false,
 | 
			
		||||
               leftPart: @Composable (() -> Unit)? = null,
 | 
			
		||||
               onCheckedChange: (Boolean) -> Unit = {}) {
 | 
			
		||||
    Row(modifier = modifier.fillMaxWidth(),
 | 
			
		||||
        horizontalArrangement = Arrangement.SpaceBetween,
 | 
			
		||||
        verticalAlignment = Alignment.CenterVertically) {
 | 
			
		||||
        if (text.isNotEmpty()) {
 | 
			
		||||
            AutoSizeText(text = text, fontSize = 16.sp)
 | 
			
		||||
        }
 | 
			
		||||
        leftPart?.invoke()
 | 
			
		||||
        Switch(checked = checked,
 | 
			
		||||
            onCheckedChange = { value -> onCheckedChange(value) },
 | 
			
		||||
            modifier = Modifier,
 | 
			
		||||
            colors = SwitchDefaults.colors(
 | 
			
		||||
                checkedThumbColor = Color(0xFFFFFFFF),
 | 
			
		||||
                checkedTrackColor = Color(0xFFF9C114),
 | 
			
		||||
 | 
			
		||||
                uncheckedThumbColor = Color(0xFFFFFFFF),
 | 
			
		||||
                uncheckedTrackColor = Color(0xFFCFD8DC),
 | 
			
		||||
                uncheckedBorderColor = Color(0xFFCFD8DC),
 | 
			
		||||
            ))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuSwitchPreview() {
 | 
			
		||||
    GakuSwitch(text = "Switch", checked = true)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.ExperimentalFoundationApi
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.material3.Tab
 | 
			
		||||
import androidx.compose.material3.TabRow
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.clip
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import androidx.compose.material3.Surface
 | 
			
		||||
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
 | 
			
		||||
import androidx.compose.ui.draw.shadow
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.compose.foundation.pager.PagerState
 | 
			
		||||
import androidx.compose.foundation.pager.rememberPagerState
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalFoundationApi::class)
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuTabRow(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    pagerState: PagerState,
 | 
			
		||||
    tabs: List<String>,
 | 
			
		||||
    onTabSelected: (index: Int) -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    val coroutineScope = rememberCoroutineScope()
 | 
			
		||||
    LaunchedEffect(pagerState.currentPage) {
 | 
			
		||||
        onTabSelected(pagerState.currentPage)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .shadow(4.dp, RoundedCornerShape(16.dp))
 | 
			
		||||
    ) {
 | 
			
		||||
        Surface(
 | 
			
		||||
            modifier = modifier
 | 
			
		||||
                .clip(RoundedCornerShape(16.dp))
 | 
			
		||||
                .shadow(4.dp),
 | 
			
		||||
            shape = RoundedCornerShape(16.dp),
 | 
			
		||||
        ) {
 | 
			
		||||
            Column {
 | 
			
		||||
                TabRow(
 | 
			
		||||
                    modifier = modifier.background(Color.Transparent),
 | 
			
		||||
                    containerColor = Color.Transparent,
 | 
			
		||||
                    selectedTabIndex = pagerState.currentPage,
 | 
			
		||||
                    indicator = @Composable { tabPositions ->
 | 
			
		||||
                        Box(
 | 
			
		||||
                            Modifier
 | 
			
		||||
                                .tabIndicatorOffset(tabPositions[pagerState.currentPage])
 | 
			
		||||
                                .height(4.dp)
 | 
			
		||||
                                .background(Color(0xFFFFA500))
 | 
			
		||||
                                .padding(horizontal = 4.dp)
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                ) {
 | 
			
		||||
                    tabs.forEachIndexed { index, title ->
 | 
			
		||||
                        Tab(
 | 
			
		||||
                            selected = pagerState.currentPage == index,
 | 
			
		||||
                            onClick = {
 | 
			
		||||
                                coroutineScope.launch {
 | 
			
		||||
                                    pagerState.scrollToPage(index)
 | 
			
		||||
//                                    pagerState.animateScrollToPage(
 | 
			
		||||
//                                        page = index,
 | 
			
		||||
//                                        animationSpec = tween(durationMillis = 250)
 | 
			
		||||
//                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            text = {
 | 
			
		||||
                                Text(
 | 
			
		||||
                                    text = title,
 | 
			
		||||
                                    color = if (pagerState.currentPage == index) Color(0xFFFFA500) else Color.Black
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalFoundationApi::class)
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuTabRowPreview(modifier: Modifier = Modifier) {
 | 
			
		||||
    val pagerState = rememberPagerState(initialPage = 1, pageCount = { 3 })
 | 
			
		||||
    GakuTabRow(modifier, pagerState, listOf("TAB 1", "TAB 2", "TAB 3")) { _ -> }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,188 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.interaction.MutableInteractionSource
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.PaddingValues
 | 
			
		||||
import androidx.compose.foundation.layout.defaultMinSize
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.foundation.text.BasicTextField
 | 
			
		||||
import androidx.compose.foundation.text.KeyboardActions
 | 
			
		||||
import androidx.compose.foundation.text.KeyboardOptions
 | 
			
		||||
import androidx.compose.material3.ExperimentalMaterial3Api
 | 
			
		||||
import androidx.compose.material3.LocalTextStyle
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.OutlinedTextFieldDefaults
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.material3.TextFieldColors
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.CompositionLocalProvider
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.graphics.Shape
 | 
			
		||||
import androidx.compose.ui.graphics.SolidColor
 | 
			
		||||
import androidx.compose.ui.semantics.semantics
 | 
			
		||||
import androidx.compose.ui.text.TextStyle
 | 
			
		||||
import androidx.compose.ui.text.input.VisualTransformation
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuTextInput(
 | 
			
		||||
    value: String,
 | 
			
		||||
    onValueChange: (String) -> Unit,
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    label: @Composable (() -> Unit)? = null,
 | 
			
		||||
    fontSize: Float = 16f,
 | 
			
		||||
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default
 | 
			
		||||
) {
 | 
			
		||||
    val shape: Shape = remember {
 | 
			
		||||
        RoundedCornerShape(
 | 
			
		||||
            topStart = 4.dp,
 | 
			
		||||
            topEnd = 16.dp,
 | 
			
		||||
            bottomEnd = 4.dp,
 | 
			
		||||
            bottomStart = 16.dp
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var localValue by remember { mutableStateOf(value) }
 | 
			
		||||
    var isUserInput by remember { mutableStateOf(false) }
 | 
			
		||||
    val textStyle = remember {
 | 
			
		||||
        TextStyle(fontSize = fontSize.sp)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LaunchedEffect(value) {
 | 
			
		||||
        if (!isUserInput) {
 | 
			
		||||
            localValue = value
 | 
			
		||||
        }
 | 
			
		||||
        isUserInput = false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
    ) {
 | 
			
		||||
        OutlinedTextFieldNoPadding(
 | 
			
		||||
            singleLine = true,
 | 
			
		||||
            value = localValue,
 | 
			
		||||
            onValueChange = { newValue ->
 | 
			
		||||
                isUserInput = true
 | 
			
		||||
                localValue = newValue
 | 
			
		||||
                onValueChange(newValue)
 | 
			
		||||
            },
 | 
			
		||||
            label = label,
 | 
			
		||||
            modifier = modifier,
 | 
			
		||||
            textStyle = textStyle,
 | 
			
		||||
            shape = shape,
 | 
			
		||||
            keyboardOptions = keyboardOptions
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalMaterial3Api::class)
 | 
			
		||||
@Composable
 | 
			
		||||
fun OutlinedTextFieldNoPadding(
 | 
			
		||||
    value: String,
 | 
			
		||||
    onValueChange: (String) -> Unit,
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    enabled: Boolean = true,
 | 
			
		||||
    readOnly: Boolean = false,
 | 
			
		||||
    textStyle: TextStyle = LocalTextStyle.current,
 | 
			
		||||
    label: @Composable (() -> Unit)? = null,
 | 
			
		||||
    placeholder: @Composable (() -> Unit)? = null,
 | 
			
		||||
    leadingIcon: @Composable (() -> Unit)? = null,
 | 
			
		||||
    trailingIcon: @Composable (() -> Unit)? = null,
 | 
			
		||||
    prefix: @Composable (() -> Unit)? = null,
 | 
			
		||||
    suffix: @Composable (() -> Unit)? = null,
 | 
			
		||||
    supportingText: @Composable (() -> Unit)? = null,
 | 
			
		||||
    isError: Boolean = false,
 | 
			
		||||
    visualTransformation: VisualTransformation = VisualTransformation.None,
 | 
			
		||||
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
 | 
			
		||||
    keyboardActions: KeyboardActions = KeyboardActions.Default,
 | 
			
		||||
    singleLine: Boolean = false,
 | 
			
		||||
    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
 | 
			
		||||
    minLines: Int = 1,
 | 
			
		||||
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
 | 
			
		||||
    shape: Shape = OutlinedTextFieldDefaults.shape,
 | 
			
		||||
    colors: TextFieldColors = OutlinedTextFieldDefaults.colors()
 | 
			
		||||
) {
 | 
			
		||||
    // If color is not provided via the text style, use content color as a default
 | 
			
		||||
    val textColor = textStyle.color
 | 
			
		||||
    val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
 | 
			
		||||
 | 
			
		||||
    CompositionLocalProvider {
 | 
			
		||||
        BasicTextField(
 | 
			
		||||
            value = value,
 | 
			
		||||
            modifier = if (label != null) {
 | 
			
		||||
                modifier
 | 
			
		||||
                    // Merge semantics at the beginning of the modifier chain to ensure padding is
 | 
			
		||||
                    // considered part of the text field.
 | 
			
		||||
                    .semantics(mergeDescendants = true) {}
 | 
			
		||||
                    .padding(top = 8.dp)
 | 
			
		||||
            } else {
 | 
			
		||||
                modifier
 | 
			
		||||
            }
 | 
			
		||||
                .defaultMinSize(
 | 
			
		||||
                    minWidth = OutlinedTextFieldDefaults.MinWidth,
 | 
			
		||||
                    minHeight = OutlinedTextFieldDefaults.MinHeight
 | 
			
		||||
                ),
 | 
			
		||||
            onValueChange = onValueChange,
 | 
			
		||||
            enabled = enabled,
 | 
			
		||||
            readOnly = readOnly,
 | 
			
		||||
            textStyle = mergedTextStyle,
 | 
			
		||||
            cursorBrush = SolidColor(if (!isError) MaterialTheme.colorScheme.primary else Color.Red),
 | 
			
		||||
            visualTransformation = visualTransformation,
 | 
			
		||||
            keyboardOptions = keyboardOptions,
 | 
			
		||||
            keyboardActions = keyboardActions,
 | 
			
		||||
            interactionSource = interactionSource,
 | 
			
		||||
            singleLine = singleLine,
 | 
			
		||||
            maxLines = maxLines,
 | 
			
		||||
            minLines = minLines,
 | 
			
		||||
            decorationBox = @Composable { innerTextField ->
 | 
			
		||||
                OutlinedTextFieldDefaults.DecorationBox(
 | 
			
		||||
                    contentPadding = PaddingValues.Absolute(left = 16.dp, right = 16.dp),
 | 
			
		||||
                    value = value,
 | 
			
		||||
                    visualTransformation = visualTransformation,
 | 
			
		||||
                    innerTextField = innerTextField,
 | 
			
		||||
                    placeholder = placeholder,
 | 
			
		||||
                    label = label,
 | 
			
		||||
                    leadingIcon = leadingIcon,
 | 
			
		||||
                    trailingIcon = trailingIcon,
 | 
			
		||||
                    prefix = prefix,
 | 
			
		||||
                    suffix = suffix,
 | 
			
		||||
                    supportingText = supportingText,
 | 
			
		||||
                    singleLine = singleLine,
 | 
			
		||||
                    enabled = enabled,
 | 
			
		||||
                    isError = isError,
 | 
			
		||||
                    interactionSource = interactionSource,
 | 
			
		||||
                    colors = colors,
 | 
			
		||||
                    container = {
 | 
			
		||||
                        OutlinedTextFieldDefaults.ContainerBox(
 | 
			
		||||
                            enabled,
 | 
			
		||||
                            isError,
 | 
			
		||||
                            interactionSource,
 | 
			
		||||
                            colors,
 | 
			
		||||
                            shape
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun GakuTextInputPreview() {
 | 
			
		||||
    GakuTextInput(modifier = Modifier.height(50.dp),
 | 
			
		||||
        fontSize = 16f,
 | 
			
		||||
        value = "123456", onValueChange = { }, label = { Text("Label") })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,103 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.pm.PackageInstaller
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.material3.AlertDialog
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.text.style.TextAlign
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import io.github.chinosk.gakumas.localify.PatchActivity
 | 
			
		||||
import io.github.chinosk.gakumas.localify.PatchCallback
 | 
			
		||||
import io.github.chinosk.gakumas.localify.R
 | 
			
		||||
import io.github.chinosk.gakumas.localify.TAG
 | 
			
		||||
import java.io.File
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun InstallDiag(context: PatchActivity?, apkFiles: List<File>, patchCallback: PatchCallback?, reservePatchFiles: Boolean,
 | 
			
		||||
                onFinish: (Int, String?) -> Unit) {
 | 
			
		||||
    // val scope = rememberCoroutineScope()
 | 
			
		||||
    // var uninstallFirst by remember { mutableStateOf(ShizukuApi.isPackageInstalledWithoutPatch(patchApp.app.packageName)) }
 | 
			
		||||
    var installing by remember { mutableStateOf(-1) }
 | 
			
		||||
    var showInstallConfirm by remember { mutableStateOf(true) }
 | 
			
		||||
 | 
			
		||||
    fun finish(code: Int, msg: String?) {
 | 
			
		||||
        patchCallback?.onLog("Install finished($code): $msg")
 | 
			
		||||
        onFinish(code, msg)
 | 
			
		||||
        if (code != PackageInstaller.STATUS_SUCCESS) {
 | 
			
		||||
            msg?.let{ patchCallback?.onFailed(it) }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun doInstall() {
 | 
			
		||||
        Log.i(TAG, "Installing app $apkFiles")
 | 
			
		||||
        installing = 1
 | 
			
		||||
        val (status, message) = PatchActivity.installSplitApks(context!!, apkFiles, reservePatchFiles, patchCallback)
 | 
			
		||||
        installing = 0
 | 
			
		||||
        Log.i(TAG, "Installation end: $status, $message")
 | 
			
		||||
        finish(status, message)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LaunchedEffect(showInstallConfirm) {
 | 
			
		||||
        if (installing == 0) {
 | 
			
		||||
            doInstall()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (showInstallConfirm) {
 | 
			
		||||
        Box {
 | 
			
		||||
            GakuGroupConfirm(
 | 
			
		||||
                title = stringResource(R.string.install),
 | 
			
		||||
                // initIsVisible = true,
 | 
			
		||||
                onCancel = {
 | 
			
		||||
                    showInstallConfirm = false
 | 
			
		||||
                    installing = -1
 | 
			
		||||
                    finish(PackageInstaller.STATUS_FAILURE, "User Cancelled.")
 | 
			
		||||
                    showInstallConfirm = true },
 | 
			
		||||
                onConfirm = {
 | 
			
		||||
                    showInstallConfirm = false
 | 
			
		||||
                    installing = 0 },
 | 
			
		||||
                contentHeightForAnimation = 500f
 | 
			
		||||
            ) {
 | 
			
		||||
                Column {
 | 
			
		||||
                    Text(stringResource(R.string.patch_finished))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (installing == 1) {
 | 
			
		||||
        AlertDialog(
 | 
			
		||||
            onDismissRequest = {},
 | 
			
		||||
            confirmButton = {},
 | 
			
		||||
            title = {
 | 
			
		||||
                Text(
 | 
			
		||||
                    modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                    text = stringResource(R.string.installing),
 | 
			
		||||
                    textAlign = TextAlign.Center
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 380)
 | 
			
		||||
@Composable
 | 
			
		||||
fun InstallDiagPreview(modifier: Modifier = Modifier) {
 | 
			
		||||
    InstallDiag(null, listOf(), null, false) { _, _ -> }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components.base
 | 
			
		||||
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.drawWithContent
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.platform.LocalInspectionMode
 | 
			
		||||
import androidx.compose.ui.text.TextStyle
 | 
			
		||||
import androidx.compose.ui.unit.TextUnit
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun AutoSizeText(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    text: String,
 | 
			
		||||
    color: Color = MaterialTheme.colorScheme.onSurface,
 | 
			
		||||
    fontSize: TextUnit = TextUnit.Unspecified,
 | 
			
		||||
    textStyle: TextStyle? = null,
 | 
			
		||||
    minSize: TextUnit = 8.sp
 | 
			
		||||
) {
 | 
			
		||||
    var scaledTextStyle by remember { mutableStateOf(textStyle ?: TextStyle(color = color, fontSize = fontSize)) }
 | 
			
		||||
    var readyToDraw by remember { mutableStateOf(false) }
 | 
			
		||||
 | 
			
		||||
    if (LocalInspectionMode.current) {
 | 
			
		||||
        Text(
 | 
			
		||||
            text,
 | 
			
		||||
            modifier,
 | 
			
		||||
            style = scaledTextStyle
 | 
			
		||||
        )
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Text(
 | 
			
		||||
        text,
 | 
			
		||||
        modifier.drawWithContent {
 | 
			
		||||
            if (readyToDraw) {
 | 
			
		||||
                drawContent()
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        style = scaledTextStyle,
 | 
			
		||||
        softWrap = false,
 | 
			
		||||
        onTextLayout = { textLayoutResult ->
 | 
			
		||||
            if (textLayoutResult.didOverflowWidth) {
 | 
			
		||||
                val newSize = (scaledTextStyle.fontSize.value - 1.sp.value).sp
 | 
			
		||||
                if (minSize <= newSize) {
 | 
			
		||||
                    scaledTextStyle = scaledTextStyle.copy(fontSize = newSize)
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    readyToDraw = true
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                readyToDraw = true
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,99 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components.base
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.animation.animateContentSize
 | 
			
		||||
import androidx.compose.animation.core.animateDpAsState
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.lifecycle.viewmodel.compose.viewModel
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.CollapsibleBoxViewModel
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun CollapsibleBox(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    collapsedHeight: Dp = 28.dp,
 | 
			
		||||
    viewModel: CollapsibleBoxViewModel = viewModel(),
 | 
			
		||||
    showExpand: Boolean = true,
 | 
			
		||||
    expandState: Boolean? = null,
 | 
			
		||||
    innerPaddingTopBottom: Dp = 0.dp,
 | 
			
		||||
    innerPaddingLeftRight: Dp = 0.dp,
 | 
			
		||||
    content: @Composable () -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    val expanded by viewModel::expanded
 | 
			
		||||
 | 
			
		||||
    // var offsetY by remember { mutableFloatStateOf(0f) }
 | 
			
		||||
 | 
			
		||||
    val animatedHeight by animateDpAsState(
 | 
			
		||||
        targetValue = if (expandState ?: expanded) Dp.Unspecified else collapsedHeight,
 | 
			
		||||
        label = "CollapsibleBox$collapsedHeight"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
            .animateContentSize()/*
 | 
			
		||||
            .pointerInput(Unit) {
 | 
			
		||||
                detectVerticalDragGestures(
 | 
			
		||||
                    onVerticalDrag = { change, dragAmount ->
 | 
			
		||||
                        change.consume()
 | 
			
		||||
                        offsetY += dragAmount
 | 
			
		||||
                        if (expanded && offsetY > 0) {
 | 
			
		||||
                            viewModel.expanded = false
 | 
			
		||||
                        } else if (!expanded && offsetY < 0) {
 | 
			
		||||
                            viewModel.expanded = true
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    onDragEnd = {
 | 
			
		||||
                        offsetY = 0f
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
            }*/
 | 
			
		||||
            .background(MaterialTheme.colorScheme.background)
 | 
			
		||||
    ) {
 | 
			
		||||
        Column(
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .height(animatedHeight)
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .padding(start = innerPaddingLeftRight, end = innerPaddingLeftRight,
 | 
			
		||||
                    top = innerPaddingTopBottom, bottom = innerPaddingTopBottom)
 | 
			
		||||
                // .fillMaxSize()
 | 
			
		||||
                .clickable {
 | 
			
		||||
                    if (!expanded && showExpand) {
 | 
			
		||||
                        viewModel.expanded = expandState ?: true
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            horizontalAlignment = Alignment.CenterHorizontally,
 | 
			
		||||
            verticalArrangement = Arrangement.Center
 | 
			
		||||
        ) {
 | 
			
		||||
            //item {
 | 
			
		||||
                if (expandState ?: expanded) {
 | 
			
		||||
                    content()
 | 
			
		||||
                }
 | 
			
		||||
                else if (showExpand) {
 | 
			
		||||
                    Text(text = "Details ↓", color = Color.Gray)
 | 
			
		||||
                }
 | 
			
		||||
            //}
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun CollapsibleBoxPreview() {
 | 
			
		||||
    CollapsibleBox(showExpand = true) {}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.components.icons
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.materialIcon
 | 
			
		||||
import androidx.compose.material.icons.materialPath
 | 
			
		||||
import androidx.compose.ui.graphics.vector.ImageVector
 | 
			
		||||
 | 
			
		||||
public val Icons.Outlined.AutoFixHigh: ImageVector
 | 
			
		||||
    get() {
 | 
			
		||||
        if (_autoFixHigh != null) {
 | 
			
		||||
            return _autoFixHigh!!
 | 
			
		||||
        }
 | 
			
		||||
        _autoFixHigh = materialIcon(name = "Outlined.AutoFixHigh") {
 | 
			
		||||
            materialPath {
 | 
			
		||||
                moveTo(20.0f, 7.0f)
 | 
			
		||||
                lineToRelative(0.94f, -2.06f)
 | 
			
		||||
                lineToRelative(2.06f, -0.94f)
 | 
			
		||||
                lineToRelative(-2.06f, -0.94f)
 | 
			
		||||
                lineToRelative(-0.94f, -2.06f)
 | 
			
		||||
                lineToRelative(-0.94f, 2.06f)
 | 
			
		||||
                lineToRelative(-2.06f, 0.94f)
 | 
			
		||||
                lineToRelative(2.06f, 0.94f)
 | 
			
		||||
                close()
 | 
			
		||||
            }
 | 
			
		||||
            materialPath {
 | 
			
		||||
                moveTo(8.5f, 7.0f)
 | 
			
		||||
                lineToRelative(0.94f, -2.06f)
 | 
			
		||||
                lineToRelative(2.06f, -0.94f)
 | 
			
		||||
                lineToRelative(-2.06f, -0.94f)
 | 
			
		||||
                lineToRelative(-0.94f, -2.06f)
 | 
			
		||||
                lineToRelative(-0.94f, 2.06f)
 | 
			
		||||
                lineToRelative(-2.06f, 0.94f)
 | 
			
		||||
                lineToRelative(2.06f, 0.94f)
 | 
			
		||||
                close()
 | 
			
		||||
            }
 | 
			
		||||
            materialPath {
 | 
			
		||||
                moveTo(20.0f, 12.5f)
 | 
			
		||||
                lineToRelative(-0.94f, 2.06f)
 | 
			
		||||
                lineToRelative(-2.06f, 0.94f)
 | 
			
		||||
                lineToRelative(2.06f, 0.94f)
 | 
			
		||||
                lineToRelative(0.94f, 2.06f)
 | 
			
		||||
                lineToRelative(0.94f, -2.06f)
 | 
			
		||||
                lineToRelative(2.06f, -0.94f)
 | 
			
		||||
                lineToRelative(-2.06f, -0.94f)
 | 
			
		||||
                close()
 | 
			
		||||
            }
 | 
			
		||||
            materialPath {
 | 
			
		||||
                moveTo(17.71f, 9.12f)
 | 
			
		||||
                lineToRelative(-2.83f, -2.83f)
 | 
			
		||||
                curveTo(14.68f, 6.1f, 14.43f, 6.0f, 14.17f, 6.0f)
 | 
			
		||||
                curveToRelative(-0.26f, 0.0f, -0.51f, 0.1f, -0.71f, 0.29f)
 | 
			
		||||
                lineTo(2.29f, 17.46f)
 | 
			
		||||
                curveToRelative(-0.39f, 0.39f, -0.39f, 1.02f, 0.0f, 1.41f)
 | 
			
		||||
                lineToRelative(2.83f, 2.83f)
 | 
			
		||||
                curveTo(5.32f, 21.9f, 5.57f, 22.0f, 5.83f, 22.0f)
 | 
			
		||||
                reflectiveCurveToRelative(0.51f, -0.1f, 0.71f, -0.29f)
 | 
			
		||||
                lineToRelative(11.17f, -11.17f)
 | 
			
		||||
                curveTo(18.1f, 10.15f, 18.1f, 9.51f, 17.71f, 9.12f)
 | 
			
		||||
                close()
 | 
			
		||||
                moveTo(14.17f, 8.42f)
 | 
			
		||||
                lineToRelative(1.41f, 1.41f)
 | 
			
		||||
                lineTo(14.41f, 11.0f)
 | 
			
		||||
                lineTo(13.0f, 9.59f)
 | 
			
		||||
                lineTo(14.17f, 8.42f)
 | 
			
		||||
                close()
 | 
			
		||||
                moveTo(5.83f, 19.59f)
 | 
			
		||||
                lineToRelative(-1.41f, -1.41f)
 | 
			
		||||
                lineTo(11.59f, 11.0f)
 | 
			
		||||
                lineTo(13.0f, 12.41f)
 | 
			
		||||
                lineTo(5.83f, 19.59f)
 | 
			
		||||
                close()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return _autoFixHigh!!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private var _autoFixHigh: ImageVector? = null
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,176 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.game_attach
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.res.ColorStateList
 | 
			
		||||
import android.content.res.Resources
 | 
			
		||||
import android.graphics.Color
 | 
			
		||||
import android.graphics.Typeface
 | 
			
		||||
import android.graphics.drawable.GradientDrawable
 | 
			
		||||
import android.graphics.drawable.LayerDrawable
 | 
			
		||||
import android.graphics.drawable.ShapeDrawable
 | 
			
		||||
import android.graphics.drawable.shapes.RectShape
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.Gravity
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.FrameLayout
 | 
			
		||||
import android.widget.ImageButton
 | 
			
		||||
import android.widget.ImageView
 | 
			
		||||
import android.widget.LinearLayout
 | 
			
		||||
import android.widget.ProgressBar
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import io.github.chinosk.gakumas.localify.TAG
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
 | 
			
		||||
import kotlinx.coroutines.DelicateCoroutinesApi
 | 
			
		||||
import kotlinx.coroutines.GlobalScope
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InitProgressUI {
 | 
			
		||||
    private var uiCreated = false
 | 
			
		||||
    private lateinit var rootView: ViewGroup
 | 
			
		||||
    private lateinit var container: LinearLayout
 | 
			
		||||
    private lateinit var assembliesProgressBar: ProgressBar
 | 
			
		||||
    private lateinit var classProgressBar: ProgressBar
 | 
			
		||||
    private lateinit var titleText: TextView
 | 
			
		||||
    private lateinit var assembliesProgressText: TextView
 | 
			
		||||
    private lateinit var classProgressText: TextView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    fun createView(context: Context) {
 | 
			
		||||
        if (uiCreated) return
 | 
			
		||||
        uiCreated = true
 | 
			
		||||
        val activity = context as? Activity ?: return
 | 
			
		||||
        rootView = activity.findViewById<ViewGroup>(android.R.id.content)
 | 
			
		||||
 | 
			
		||||
        container = LinearLayout(context).apply {
 | 
			
		||||
            orientation = LinearLayout.VERTICAL
 | 
			
		||||
            layoutParams = FrameLayout.LayoutParams(
 | 
			
		||||
                FrameLayout.LayoutParams.MATCH_PARENT,
 | 
			
		||||
                FrameLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
            ).apply {
 | 
			
		||||
                gravity = Gravity.TOP or Gravity.END
 | 
			
		||||
                marginEnd = 20
 | 
			
		||||
                marginStart = 20
 | 
			
		||||
                topMargin = 100
 | 
			
		||||
            }
 | 
			
		||||
            setBackgroundColor(Color.WHITE)
 | 
			
		||||
            setPadding(20, 20, 20, 20)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set up the container layout
 | 
			
		||||
        assembliesProgressBar = ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal).apply {
 | 
			
		||||
            layoutParams = LinearLayout.LayoutParams(
 | 
			
		||||
                LinearLayout.LayoutParams.MATCH_PARENT,
 | 
			
		||||
                LinearLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
            ).apply {
 | 
			
		||||
                topMargin = 20
 | 
			
		||||
            }
 | 
			
		||||
            max = 100
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set up the class progress bar
 | 
			
		||||
        classProgressBar = ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal).apply {
 | 
			
		||||
            layoutParams = LinearLayout.LayoutParams(
 | 
			
		||||
                LinearLayout.LayoutParams.MATCH_PARENT,
 | 
			
		||||
                LinearLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
            ).apply {
 | 
			
		||||
                topMargin = 20
 | 
			
		||||
            }
 | 
			
		||||
            max = 100
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assembliesProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FFF89400"))
 | 
			
		||||
        classProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FFF89400"))
 | 
			
		||||
 | 
			
		||||
        // Set up the text views
 | 
			
		||||
        titleText = TextView(context).apply {
 | 
			
		||||
            layoutParams = FrameLayout.LayoutParams(
 | 
			
		||||
                FrameLayout.LayoutParams.WRAP_CONTENT,
 | 
			
		||||
                FrameLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
            ).apply {
 | 
			
		||||
                topMargin = 20
 | 
			
		||||
                gravity = Gravity.CENTER_HORIZONTAL
 | 
			
		||||
            }
 | 
			
		||||
            setTextColor(Color.BLACK)
 | 
			
		||||
            text = "Initializing"
 | 
			
		||||
            textSize = 20f
 | 
			
		||||
            setTypeface(typeface, Typeface.BOLD)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val textLayout = FrameLayout.LayoutParams(
 | 
			
		||||
            FrameLayout.LayoutParams.WRAP_CONTENT,
 | 
			
		||||
            FrameLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
        ).apply {
 | 
			
		||||
            topMargin = 20
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assembliesProgressText = TextView(context).apply {
 | 
			
		||||
            layoutParams = textLayout
 | 
			
		||||
            setTextColor(Color.BLACK)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        classProgressText = TextView(context).apply {
 | 
			
		||||
            layoutParams = textLayout
 | 
			
		||||
            setTextColor(Color.BLACK)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add container to the root view
 | 
			
		||||
        context.runOnUiThread {
 | 
			
		||||
            // Add views to the container
 | 
			
		||||
            container.addView(titleText)
 | 
			
		||||
            container.addView(assembliesProgressText)
 | 
			
		||||
            container.addView(assembliesProgressBar)
 | 
			
		||||
            container.addView(classProgressText)
 | 
			
		||||
            container.addView(classProgressBar)
 | 
			
		||||
 | 
			
		||||
            rootView.addView(container)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    @OptIn(DelicateCoroutinesApi::class)
 | 
			
		||||
    fun finishLoad(context: Activity) {
 | 
			
		||||
        if (!uiCreated) return
 | 
			
		||||
        uiCreated = false
 | 
			
		||||
        GlobalScope.launch {
 | 
			
		||||
            context.runOnUiThread {
 | 
			
		||||
                assembliesProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FF28B463"))
 | 
			
		||||
                classProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FF28B463"))
 | 
			
		||||
                titleText.text = "Finished"
 | 
			
		||||
            }
 | 
			
		||||
            delay(1500L)
 | 
			
		||||
            context.runOnUiThread {
 | 
			
		||||
                rootView.removeView(container)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun removeView(context: Activity) {
 | 
			
		||||
        if (!uiCreated) return
 | 
			
		||||
        uiCreated = false
 | 
			
		||||
        context.runOnUiThread {
 | 
			
		||||
            rootView.removeView(container)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    fun updateData(context: Activity) {
 | 
			
		||||
        if (!uiCreated) return
 | 
			
		||||
        //return
 | 
			
		||||
 | 
			
		||||
        context.runOnUiThread {
 | 
			
		||||
            val assembliesProgress = NativeInitProgress.assembliesProgress
 | 
			
		||||
            val classProgress = NativeInitProgress.classProgress
 | 
			
		||||
 | 
			
		||||
            assembliesProgressText.text = "${assembliesProgress.current}/${assembliesProgress.total}"
 | 
			
		||||
            classProgressText.text = "${classProgress.current}/${classProgress.total}"
 | 
			
		||||
 | 
			
		||||
            assembliesProgressBar.setProgress((assembliesProgress.current * 100 / assembliesProgress.total).toInt(), true)
 | 
			
		||||
            classProgressBar.setProgress((classProgress.current * 100 / classProgress.total).toInt(), true)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,148 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.pages
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.compose.foundation.Image
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.BoxWithConstraints
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.sizeIn
 | 
			
		||||
import androidx.compose.foundation.lazy.LazyColumn
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateListOf
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.graphics.painter.Painter
 | 
			
		||||
import androidx.compose.ui.layout.ContentScale
 | 
			
		||||
import androidx.compose.ui.platform.LocalDensity
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
import io.github.chinosk.gakumas.localify.MainActivity
 | 
			
		||||
import io.github.chinosk.gakumas.localify.R
 | 
			
		||||
import io.github.chinosk.gakumas.localify.TAG
 | 
			
		||||
import io.github.chinosk.gakumas.localify.getMainUIConfirmState
 | 
			
		||||
import io.github.chinosk.gakumas.localify.getProgramConfigState
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupConfirm
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun MainUI(modifier: Modifier = Modifier, context: MainActivity? = null,
 | 
			
		||||
           previewData: GakumasConfig? = null) {
 | 
			
		||||
    val imagePainter = painterResource(R.drawable.bg_pattern)
 | 
			
		||||
    var versionInfo by remember {
 | 
			
		||||
        mutableStateOf(context?.getVersion() ?: listOf("", "Unknown"))
 | 
			
		||||
    }
 | 
			
		||||
    // val config = getConfigState(context, previewData)
 | 
			
		||||
    val confirmState by getMainUIConfirmState(context, null)
 | 
			
		||||
    val programConfig by getProgramConfigState(context)
 | 
			
		||||
 | 
			
		||||
    LaunchedEffect(programConfig) {
 | 
			
		||||
        versionInfo = context?.getVersion() ?: listOf("", "Unknown")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxSize()
 | 
			
		||||
            .background(Color(0xFFFDFDFD))
 | 
			
		||||
    ) {
 | 
			
		||||
        val screenH = imageRepeater(
 | 
			
		||||
            painter = imagePainter,
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .align(Alignment.TopCenter)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        Column(
 | 
			
		||||
            modifier = modifier
 | 
			
		||||
                .fillMaxSize()
 | 
			
		||||
                .padding(10.dp, 10.dp, 10.dp, 0.dp),
 | 
			
		||||
            verticalArrangement = Arrangement.Top
 | 
			
		||||
        ) {
 | 
			
		||||
            Text(text = "Gakumas Localify ${versionInfo[0]}", fontSize = 18.sp)
 | 
			
		||||
            Text(text = "Assets version: ${versionInfo[1]}", fontSize = 13.sp)
 | 
			
		||||
 | 
			
		||||
            SettingsTabs(modifier, listOf(stringResource(R.string.about), stringResource(R.string.home),
 | 
			
		||||
                stringResource(R.string.advanced_settings)),
 | 
			
		||||
                context = context, previewData = previewData, screenH = screenH)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (confirmState.isShow) {
 | 
			
		||||
            GakuGroupConfirm(
 | 
			
		||||
                title = confirmState.title,
 | 
			
		||||
                onCancel = { confirmState.onCancel() },
 | 
			
		||||
                onConfirm = { confirmState.onConfirm() },
 | 
			
		||||
                contentHeightForAnimation = screenH.value * 1.8f
 | 
			
		||||
            ) {
 | 
			
		||||
                LazyColumn(modifier =
 | 
			
		||||
                Modifier.sizeIn(maxHeight = (screenH.value * 0.45f).dp)
 | 
			
		||||
                    .fillMaxWidth()) {
 | 
			
		||||
                    item {
 | 
			
		||||
                        Text(confirmState.content)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun imageRepeater(
 | 
			
		||||
    painter: Painter,
 | 
			
		||||
    modifier: Modifier = Modifier
 | 
			
		||||
): Dp {
 | 
			
		||||
    val density = LocalDensity.current
 | 
			
		||||
    val imageHeightPx = painter.intrinsicSize.height
 | 
			
		||||
    val imageHeightDp = with(density) { imageHeightPx.toDp() }
 | 
			
		||||
    var retMaxH = 1080.dp
 | 
			
		||||
    BoxWithConstraints(modifier = modifier) {
 | 
			
		||||
        retMaxH = maxHeight
 | 
			
		||||
        val screenHeight = maxHeight
 | 
			
		||||
        val repeatCount = (screenHeight / imageHeightDp).toInt() + 1
 | 
			
		||||
 | 
			
		||||
        Column {
 | 
			
		||||
            repeat(repeatCount) {
 | 
			
		||||
                Image(
 | 
			
		||||
                    painter = painter,
 | 
			
		||||
                    contentDescription = null,
 | 
			
		||||
                    contentScale = ContentScale.Crop,
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .fillMaxWidth()
 | 
			
		||||
                        .height(imageHeightDp)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return retMaxH
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 380)
 | 
			
		||||
@Composable
 | 
			
		||||
fun MainUIPreview(modifier: Modifier = Modifier) {
 | 
			
		||||
    val previewConfig = GakumasConfig()
 | 
			
		||||
    previewConfig.enabled = true
 | 
			
		||||
 | 
			
		||||
    GakumasLocalifyTheme {
 | 
			
		||||
        MainUI(previewData = previewConfig)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,356 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.pages
 | 
			
		||||
 | 
			
		||||
import android.content.pm.PackageManager
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.activity.compose.rememberLauncherForActivityResult
 | 
			
		||||
import androidx.activity.result.contract.ActivityResultContracts
 | 
			
		||||
import androidx.compose.animation.animateContentSize
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.size
 | 
			
		||||
import androidx.compose.foundation.layout.sizeIn
 | 
			
		||||
import androidx.compose.foundation.lazy.LazyColumn
 | 
			
		||||
import androidx.compose.foundation.shape.CircleShape
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.outlined.CheckCircle
 | 
			
		||||
import androidx.compose.material.icons.outlined.Warning
 | 
			
		||||
import androidx.compose.material3.CardDefaults
 | 
			
		||||
import androidx.compose.material3.ElevatedCard
 | 
			
		||||
import androidx.compose.material3.FloatingActionButton
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.DisposableEffect
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateListOf
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.saveable.Saver
 | 
			
		||||
import androidx.compose.runtime.saveable.rememberSaveable
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.runtime.snapshots.SnapshotStateList
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
import io.github.chinosk.gakumas.localify.R
 | 
			
		||||
import io.github.chinosk.gakumas.localify.TAG
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuApi
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupConfirm
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuRadio
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.icons.AutoFixHigh
 | 
			
		||||
import org.lsposed.lspatch.share.LSPConfig
 | 
			
		||||
import rikka.shizuku.Shizuku
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
data class LogText(var msg: String, val isErr: Boolean)
 | 
			
		||||
 | 
			
		||||
private val shizukuListener: (Int, Int) -> Unit = { _, grantResult ->
 | 
			
		||||
    ShizukuApi.isPermissionGranted = grantResult == PackageManager.PERMISSION_GRANTED
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
val LogTextListSaver = Saver<SnapshotStateList<LogText>, List<String>>(
 | 
			
		||||
    save = { logTextList -> logTextList.map { "${it.msg},${it.isErr}" } },
 | 
			
		||||
    restore = { savedStrings ->
 | 
			
		||||
        val restoredList = mutableStateListOf<LogText>()
 | 
			
		||||
        savedStrings.forEach { savedString ->
 | 
			
		||||
            val parts = savedString.split(",")
 | 
			
		||||
            restoredList.add(LogText(parts[0], parts[1].toBoolean()))
 | 
			
		||||
        }
 | 
			
		||||
        restoredList
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun PatchPage(modifier: Modifier = Modifier,
 | 
			
		||||
              content: (@Composable () -> Unit)? = null,
 | 
			
		||||
              onClickPatch: (selectFiles: List<Uri>, isLocalMode: Boolean, isDebuggable: Boolean,
 | 
			
		||||
                             reservePatchFiles: Boolean,
 | 
			
		||||
                             onFinishCallback: () -> Unit,
 | 
			
		||||
                             onLogCallback: (msg: String, isErr: Boolean) -> Unit) -> Unit) {
 | 
			
		||||
    LaunchedEffect(Unit) {
 | 
			
		||||
        Shizuku.addRequestPermissionResultListener(shizukuListener)
 | 
			
		||||
    }
 | 
			
		||||
    DisposableEffect(Unit) {
 | 
			
		||||
        onDispose {
 | 
			
		||||
            Shizuku.removeRequestPermissionResultListener(shizukuListener)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val imagePainter = painterResource(R.drawable.bg_pattern)
 | 
			
		||||
 | 
			
		||||
    var isPatchLocalMode by rememberSaveable { mutableStateOf(true) }
 | 
			
		||||
    var isPatchDebuggable by rememberSaveable { mutableStateOf(true) }
 | 
			
		||||
    var isPatching by rememberSaveable { mutableStateOf(false) }
 | 
			
		||||
    var reservePatchFiles by rememberSaveable { mutableStateOf(false) }
 | 
			
		||||
    var showUninstallConfirm by remember { mutableStateOf(false) }
 | 
			
		||||
 | 
			
		||||
    val logMsgList = rememberSaveable(saver = LogTextListSaver) { mutableStateListOf(LogText("Patcher Logs", false)) }
 | 
			
		||||
 | 
			
		||||
    fun addLogMsg(msg: String, isErr: Boolean) {
 | 
			
		||||
        val length = logMsgList.size
 | 
			
		||||
        if (length == 0) {
 | 
			
		||||
            logMsgList.add(LogText(msg, isErr))
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            val lastLog = logMsgList[length - 1]
 | 
			
		||||
            if (lastLog.isErr == isErr) {
 | 
			
		||||
                // lastLog.msg += "\n${msg}"
 | 
			
		||||
                logMsgList[length - 1] = LogText("${lastLog.msg}\n$msg", isErr)
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                logMsgList.add(LogText(msg, isErr))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val storageLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { apks ->
 | 
			
		||||
        Log.d(TAG, apks.toString())
 | 
			
		||||
        if (apks.isEmpty()) {
 | 
			
		||||
            return@rememberLauncherForActivityResult
 | 
			
		||||
        }
 | 
			
		||||
        isPatching = true
 | 
			
		||||
        logMsgList.clear()
 | 
			
		||||
        onClickPatch(apks, isPatchLocalMode, isPatchDebuggable, reservePatchFiles, { isPatching = false }) { msg, err ->
 | 
			
		||||
            addLogMsg(msg, err)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    content?.let { it() }
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxSize()
 | 
			
		||||
            .background(Color(0xFFFDFDFD))
 | 
			
		||||
    ) {
 | 
			
		||||
        val screenH = imageRepeater(
 | 
			
		||||
            painter = imagePainter,
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .align(Alignment.TopCenter)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        Column(
 | 
			
		||||
            modifier = modifier
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .padding(10.dp, 10.dp, 10.dp, 0.dp),
 | 
			
		||||
            verticalArrangement = Arrangement.Top,
 | 
			
		||||
            horizontalAlignment = Alignment.CenterHorizontally
 | 
			
		||||
        ) {
 | 
			
		||||
            Column(modifier = modifier.fillMaxWidth()
 | 
			
		||||
                .padding(10.dp, 10.dp, 10.dp, 0.dp)) {
 | 
			
		||||
                Text(text = "Gakumas Localify Patcher", fontSize = 18.sp)
 | 
			
		||||
                Text(text = "LSPatch version: ${LSPConfig.instance.VERSION_NAME} (${LSPConfig.instance.VERSION_CODE})", fontSize = 13.sp)
 | 
			
		||||
                Text(text = "Framework version: ${LSPConfig.instance.CORE_VERSION_NAME} (${LSPConfig.instance.CORE_VERSION_CODE}), API ${LSPConfig.instance.API_CODE}", fontSize = 13.sp)
 | 
			
		||||
                // Text(text = "Shuzuku: ${ShizukuApi.isBinderAvailable} ${ShizukuApi.isPermissionGranted}", fontSize = 13.sp)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer(Modifier.height(6.dp))
 | 
			
		||||
 | 
			
		||||
            GakuGroupBox(modifier = modifier, "Shizuku", contentPadding = 0.dp) {
 | 
			
		||||
                ElevatedCard(
 | 
			
		||||
                    shape = RoundedCornerShape(
 | 
			
		||||
                        bottomStart = 16.dp,
 | 
			
		||||
                        bottomEnd = 8.dp
 | 
			
		||||
                    ),
 | 
			
		||||
                    colors = CardDefaults.elevatedCardColors(containerColor = run {
 | 
			
		||||
                        if (ShizukuApi.isPermissionGranted) MaterialTheme.colorScheme.background
 | 
			
		||||
                        else MaterialTheme.colorScheme.errorContainer
 | 
			
		||||
                    })
 | 
			
		||||
                ) {
 | 
			
		||||
                    Row(
 | 
			
		||||
                        modifier = Modifier
 | 
			
		||||
                            .fillMaxWidth()
 | 
			
		||||
                            .clickable {
 | 
			
		||||
                                if (ShizukuApi.isBinderAvailable && !ShizukuApi.isPermissionGranted) {
 | 
			
		||||
                                    Shizuku.requestPermission(114514)
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            .padding(18.dp, 10.dp, 18.dp, 14.dp),
 | 
			
		||||
                        verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
                    ) {
 | 
			
		||||
                        if (ShizukuApi.isPermissionGranted) {
 | 
			
		||||
                            Icon(Icons.Outlined.CheckCircle, stringResource(R.string.shizuku_available))
 | 
			
		||||
                            Column(Modifier.padding(start = 20.dp)) {
 | 
			
		||||
                                Text(
 | 
			
		||||
                                    text = stringResource(R.string.shizuku_available),
 | 
			
		||||
                                    style = MaterialTheme.typography.titleMedium
 | 
			
		||||
                                )
 | 
			
		||||
                                Spacer(Modifier.height(4.dp))
 | 
			
		||||
                                Text(
 | 
			
		||||
                                    text = "API " + Shizuku.getVersion(),
 | 
			
		||||
                                    style = MaterialTheme.typography.bodyMedium
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            Icon(Icons.Outlined.Warning, stringResource(R.string.shizuku_unavailable))
 | 
			
		||||
                            Column(Modifier.padding(start = 20.dp)) {
 | 
			
		||||
                                Text(
 | 
			
		||||
                                    text = stringResource(R.string.shizuku_unavailable),
 | 
			
		||||
                                    style = MaterialTheme.typography.titleMedium
 | 
			
		||||
                                )
 | 
			
		||||
                                Spacer(Modifier.height(4.dp))
 | 
			
		||||
                                Text(
 | 
			
		||||
                                    text = stringResource(R.string.home_shizuku_warning),
 | 
			
		||||
                                    style = MaterialTheme.typography.bodyMedium
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer(Modifier.height(6.dp))
 | 
			
		||||
 | 
			
		||||
            Box(modifier = Modifier.weight(1f)) {
 | 
			
		||||
                GakuGroupBox(modifier = modifier, stringResource(R.string.game_patch)) {
 | 
			
		||||
 | 
			
		||||
                    Column(modifier = Modifier,
 | 
			
		||||
                        verticalArrangement = Arrangement.spacedBy(6.dp)) {
 | 
			
		||||
 | 
			
		||||
                        Text(stringResource(R.string.patch_mode))
 | 
			
		||||
                        Row(modifier = modifier.fillMaxWidth(),
 | 
			
		||||
                            horizontalArrangement = Arrangement.spacedBy(6.dp)) {
 | 
			
		||||
                            val radioModifier = remember {
 | 
			
		||||
                                modifier
 | 
			
		||||
                                    .height(40.dp)
 | 
			
		||||
                                    .weight(1f)
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            GakuRadio(modifier = radioModifier,
 | 
			
		||||
                                text = stringResource(R.string.patch_local), selected = isPatchLocalMode,
 | 
			
		||||
                                onClick = { isPatchLocalMode = true })
 | 
			
		||||
 | 
			
		||||
                            GakuRadio(modifier = radioModifier,
 | 
			
		||||
                                text = stringResource(R.string.patch_integrated), selected = !isPatchLocalMode,
 | 
			
		||||
                                onClick = { isPatchLocalMode = false })
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        CollapsibleBox(modifier = modifier,
 | 
			
		||||
                            expandState = true,
 | 
			
		||||
                            collapsedHeight = 0.dp,
 | 
			
		||||
                            showExpand = false
 | 
			
		||||
                        ) {
 | 
			
		||||
                            Box(modifier = Modifier.fillMaxWidth()) {
 | 
			
		||||
                                Text(text =  stringResource(
 | 
			
		||||
                                    if (isPatchLocalMode)
 | 
			
		||||
                                        R.string.patch_local_desc
 | 
			
		||||
                                    else
 | 
			
		||||
                                        R.string.patch_integrated_desc
 | 
			
		||||
                                ), color = Color.Gray, fontSize = 12.sp
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        GakuSwitch(modifier, stringResource(R.string.patch_debuggable), checked = isPatchDebuggable) {
 | 
			
		||||
                            isPatchDebuggable = !isPatchDebuggable
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        GakuSwitch(modifier, stringResource(R.string.reserve_patched), checked = reservePatchFiles) {
 | 
			
		||||
                            reservePatchFiles = !reservePatchFiles
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        Text(stringResource(R.string.support_file_types))
 | 
			
		||||
 | 
			
		||||
                        CollapsibleBox(modifier = modifier,
 | 
			
		||||
                            expandState = true,
 | 
			
		||||
                            collapsedHeight = 0.dp,
 | 
			
		||||
                            showExpand = false
 | 
			
		||||
                        ) {
 | 
			
		||||
                            Box(modifier = Modifier) {
 | 
			
		||||
                                LazyColumn(modifier = Modifier
 | 
			
		||||
                                    .fillMaxWidth()
 | 
			
		||||
                                    .sizeIn(maxHeight = screenH),
 | 
			
		||||
                                    reverseLayout = true) {
 | 
			
		||||
                                    logMsgList.asReversed().forEach { logText ->
 | 
			
		||||
                                        item {
 | 
			
		||||
                                            Text(modifier = Modifier.animateContentSize(),
 | 
			
		||||
                                                text = logText.msg,
 | 
			
		||||
                                                color = if (logText.isErr) Color.Red else Color.Black,
 | 
			
		||||
                                                fontSize = 12.sp)
 | 
			
		||||
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        Spacer(Modifier.height(0.dp))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer(Modifier.height(12.dp))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FloatingActionButton(
 | 
			
		||||
            onClick = { if (!isPatching) {
 | 
			
		||||
                if (ShizukuApi.isPermissionGranted &&
 | 
			
		||||
                    ShizukuApi.isPackageInstalledWithoutPatch("com.bandainamcoent.idolmaster_gakuen")) {
 | 
			
		||||
                    showUninstallConfirm = true
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    storageLauncher.launch(arrayOf("*/*"))
 | 
			
		||||
                }
 | 
			
		||||
            } },
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .align(Alignment.BottomEnd)
 | 
			
		||||
                .padding(16.dp),
 | 
			
		||||
            containerColor = if (isPatching) Color.Gray else MaterialTheme.colorScheme.primary,
 | 
			
		||||
            shape = CircleShape
 | 
			
		||||
        ) {
 | 
			
		||||
            Icon(modifier = Modifier.size(24.dp),
 | 
			
		||||
                imageVector = Icons.Outlined.AutoFixHigh,
 | 
			
		||||
                contentDescription = "GotoPatch")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (showUninstallConfirm) {
 | 
			
		||||
            GakuGroupConfirm(
 | 
			
		||||
                title = stringResource(R.string.warning),
 | 
			
		||||
                onCancel = { showUninstallConfirm = false },
 | 
			
		||||
                onConfirm = {
 | 
			
		||||
                    showUninstallConfirm = false
 | 
			
		||||
                    storageLauncher.launch(arrayOf("*/*"))},
 | 
			
		||||
                contentHeightForAnimation = screenH.value
 | 
			
		||||
            ) {
 | 
			
		||||
                Column {
 | 
			
		||||
                    Text(stringResource(R.string.patch_uninstall_text))
 | 
			
		||||
                    Text(stringResource(R.string.patch_uninstall_confirm))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 680)
 | 
			
		||||
@Composable
 | 
			
		||||
fun PatchPagePreview(modifier: Modifier = Modifier) {
 | 
			
		||||
    PatchPage(modifier) { _, _, _, _, _, _ -> }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,107 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.pages
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.ExperimentalFoundationApi
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxHeight
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.size
 | 
			
		||||
import androidx.compose.foundation.pager.HorizontalPager
 | 
			
		||||
import androidx.compose.foundation.pager.rememberPagerState
 | 
			
		||||
import androidx.compose.foundation.shape.CircleShape
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
 | 
			
		||||
import androidx.compose.material3.FloatingActionButton
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import io.github.chinosk.gakumas.localify.MainActivity
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.onClickStartGame
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuTabRow
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.icons.AutoFixHigh
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.pages.subPages.AboutPage
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.pages.subPages.AdvanceSettingsPage
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.pages.subPages.HomePage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalFoundationApi::class)
 | 
			
		||||
@Composable
 | 
			
		||||
fun SettingsTabs(modifier: Modifier = Modifier,
 | 
			
		||||
                 titles: List<String>,
 | 
			
		||||
                 context: MainActivity? = null,
 | 
			
		||||
                 previewData: GakumasConfig? = null,
 | 
			
		||||
                 screenH: Dp = 1080.dp
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    val pagerState = rememberPagerState(initialPage = 1, pageCount = { titles.size })
 | 
			
		||||
 | 
			
		||||
    Box {
 | 
			
		||||
        HorizontalPager(
 | 
			
		||||
            state = pagerState,
 | 
			
		||||
            modifier = modifier.fillMaxSize(),
 | 
			
		||||
            pageSpacing = 10.dp
 | 
			
		||||
        ) { page ->
 | 
			
		||||
            Column(modifier = modifier
 | 
			
		||||
                .padding(5.dp)
 | 
			
		||||
                .fillMaxHeight(),
 | 
			
		||||
                verticalArrangement = Arrangement.Top,
 | 
			
		||||
                horizontalAlignment = Alignment.CenterHorizontally) {
 | 
			
		||||
                when (page) {
 | 
			
		||||
                    0 -> AboutPage(modifier, context = context, previewData = previewData, screenH = screenH)
 | 
			
		||||
                    1 -> HomePage(modifier, context = context, previewData = previewData, screenH = screenH)
 | 
			
		||||
                    2 -> AdvanceSettingsPage(modifier, context = context, previewData = previewData, screenH = screenH)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Box(
 | 
			
		||||
            Modifier
 | 
			
		||||
                .align(Alignment.BottomCenter)
 | 
			
		||||
                .padding(bottom = 6.dp)) {
 | 
			
		||||
            Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
 | 
			
		||||
                Row(modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                    horizontalArrangement = Arrangement.SpaceBetween) {
 | 
			
		||||
                    FloatingActionButton(
 | 
			
		||||
                        onClick = { context?.gotoPatchActivity() },
 | 
			
		||||
                        // modifier = Modifier.align(Alignment.End),
 | 
			
		||||
                        containerColor = MaterialTheme.colorScheme.primary,
 | 
			
		||||
                        shape = CircleShape
 | 
			
		||||
                    ) {
 | 
			
		||||
                        Icon(modifier = Modifier.size(24.dp),
 | 
			
		||||
                            imageVector = Icons.Outlined.AutoFixHigh,
 | 
			
		||||
                            contentDescription = "GotoPatch")
 | 
			
		||||
                    }
 | 
			
		||||
                    FloatingActionButton(
 | 
			
		||||
                        onClick = { context?.onClickStartGame() },
 | 
			
		||||
                        //modifier = Modifier.align(Alignment.End),
 | 
			
		||||
                        containerColor = MaterialTheme.colorScheme.primary,
 | 
			
		||||
                        shape = CircleShape
 | 
			
		||||
                    ) {
 | 
			
		||||
                        Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
 | 
			
		||||
                            contentDescription = "StartGame")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                GakuTabRow(modifier, pagerState, titles) { }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, heightDp = 760)
 | 
			
		||||
@Composable
 | 
			
		||||
fun SettingTabsPreview(modifier: Modifier = Modifier) {
 | 
			
		||||
    SettingsTabs(titles = listOf("TAB 1", "TAB 2", "TAB 3"), previewData = GakumasConfig())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
import androidx.compose.foundation.Image
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.layout.ContentScale
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.navigation.NavController
 | 
			
		||||
import io.github.chinosk.gakumas.localify.R
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun SplashScreen(navController: NavController) {
 | 
			
		||||
    /*Image(
 | 
			
		||||
        painter = painterResource(id = R.drawable.splash_image),
 | 
			
		||||
        contentDescription = null,
 | 
			
		||||
        modifier = Modifier.fillMaxSize(),
 | 
			
		||||
        contentScale = ContentScale.FillHeight
 | 
			
		||||
    )*/
 | 
			
		||||
 | 
			
		||||
    LaunchedEffect(Unit) {
 | 
			
		||||
        kotlinx.coroutines.delay(100)
 | 
			
		||||
 | 
			
		||||
        navController.navigate("main") {
 | 
			
		||||
            popUpTo("splash") { inclusive = true }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,221 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.pages.subPages
 | 
			
		||||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.Image
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.sizeIn
 | 
			
		||||
import androidx.compose.foundation.lazy.LazyColumn
 | 
			
		||||
import androidx.compose.material3.HorizontalDivider
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.platform.LocalContext
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
import coil.ImageLoader
 | 
			
		||||
import coil.compose.rememberAsyncImagePainter
 | 
			
		||||
import coil.decode.SvgDecoder
 | 
			
		||||
import coil.request.ImageRequest
 | 
			
		||||
import coil.size.Size
 | 
			
		||||
import io.github.chinosk.gakumas.localify.MainActivity
 | 
			
		||||
import io.github.chinosk.gakumas.localify.R
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.convertToString
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.json
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.AboutPageConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuButton
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun AboutPage(modifier: Modifier = Modifier,
 | 
			
		||||
             context: MainActivity? = null,
 | 
			
		||||
             previewData: GakumasConfig? = null,
 | 
			
		||||
             bottomSpacerHeight: Dp = 120.dp,
 | 
			
		||||
             screenH: Dp = 1080.dp) {
 | 
			
		||||
    // val config = getConfigState(context, previewData)
 | 
			
		||||
    val contributorInfo = remember {
 | 
			
		||||
        val dataJsonString = context?.getString(R.string.about_contributors_asset_file)?.let {
 | 
			
		||||
            convertToString(context.assets?.open(it))
 | 
			
		||||
        }
 | 
			
		||||
        dataJsonString?.let { json.decodeFromString<AboutPageConfig>(it) }
 | 
			
		||||
            ?: AboutPageConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LazyColumn(modifier = modifier
 | 
			
		||||
        .sizeIn(maxHeight = screenH)
 | 
			
		||||
        .fillMaxWidth(),
 | 
			
		||||
        verticalArrangement = Arrangement.spacedBy(16.dp)
 | 
			
		||||
    ) {
 | 
			
		||||
        item {
 | 
			
		||||
            HorizontalDivider(
 | 
			
		||||
                thickness = 1.dp,
 | 
			
		||||
                color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            Text(stringResource(R.string.about_warn_title), fontSize = 24.sp, color = MaterialTheme.colorScheme.error)
 | 
			
		||||
            Text(stringResource(R.string.about_warn_p1))
 | 
			
		||||
            Text(stringResource(R.string.about_warn_p2))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            HorizontalDivider(
 | 
			
		||||
                thickness = 1.dp,
 | 
			
		||||
                color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            Text(stringResource(R.string.about_about_title), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer)
 | 
			
		||||
            Text(stringResource(R.string.about_about_p1))
 | 
			
		||||
            Text(stringResource(R.string.about_about_p2))
 | 
			
		||||
            Row(modifier = Modifier
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .padding(
 | 
			
		||||
                    start = 8.dp, end = 8.dp, top = 8.dp, bottom = 0.dp
 | 
			
		||||
                )) {
 | 
			
		||||
                GakuButton(text = "Github", modifier = modifier
 | 
			
		||||
                    .weight(1f)
 | 
			
		||||
                    .sizeIn(maxWidth = 600.dp), onClick = {
 | 
			
		||||
                    context?.openUrl(contributorInfo.plugin_repo)
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            HorizontalDivider(
 | 
			
		||||
                thickness = 1.dp,
 | 
			
		||||
                color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            LazyColumn(modifier = modifier
 | 
			
		||||
                .sizeIn(maxHeight = screenH)
 | 
			
		||||
                .fillMaxWidth()) {
 | 
			
		||||
                item {
 | 
			
		||||
                    Text(stringResource(R.string.project_contribution), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer)
 | 
			
		||||
                }
 | 
			
		||||
                for (contributor in contributorInfo.main_contributors) {
 | 
			
		||||
                    item {
 | 
			
		||||
                        Row(modifier = Modifier
 | 
			
		||||
                            .fillMaxWidth()
 | 
			
		||||
                            .padding(0.dp, 8.dp, 8.dp, 8.dp),
 | 
			
		||||
                            horizontalArrangement = Arrangement.spacedBy(8.dp),
 | 
			
		||||
                            verticalAlignment = Alignment.CenterVertically) {
 | 
			
		||||
                            Text(contributor.name, fontSize = 16.sp)
 | 
			
		||||
                            for (link in contributor.links) {
 | 
			
		||||
                                GakuButton(text = link.name, modifier = modifier.height(40.dp),
 | 
			
		||||
                                    onClick = {
 | 
			
		||||
                                        context?.openUrl(link.link)
 | 
			
		||||
                                    })
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            Text(stringResource(R.string.contributors), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer)
 | 
			
		||||
 | 
			
		||||
            Text(stringResource(R.string.plugin_code), fontSize = 16.sp)
 | 
			
		||||
            NetworkSvgImage(
 | 
			
		||||
                url = contributorInfo.contrib_img.plugin,
 | 
			
		||||
                contentDescription = "plugin-contrib"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            Spacer(modifier = Modifier.height(4.dp))
 | 
			
		||||
            Text(stringResource(R.string.translation_repository), fontSize = 16.sp)
 | 
			
		||||
            NetworkSvgImage(
 | 
			
		||||
                url = contributorInfo.contrib_img.translation,
 | 
			
		||||
                contentDescription = "translation-contrib"
 | 
			
		||||
            )
 | 
			
		||||
            contributorInfo.contrib_img.translations.forEach { url ->
 | 
			
		||||
                Spacer(modifier = Modifier.height(2.dp))
 | 
			
		||||
                NetworkSvgImage(
 | 
			
		||||
                    url = url,
 | 
			
		||||
                    contentDescription = "translation-contrib"
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            HorizontalDivider(
 | 
			
		||||
                thickness = 1.dp,
 | 
			
		||||
                color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            Spacer(modifier = modifier.height(bottomSpacerHeight))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun NetworkImage(
 | 
			
		||||
    url: String,
 | 
			
		||||
    contentDescription: String?,
 | 
			
		||||
    modifier: Modifier = Modifier
 | 
			
		||||
) {
 | 
			
		||||
    val painter = rememberAsyncImagePainter(model = ImageRequest.Builder(LocalContext.current)
 | 
			
		||||
        .data(url)
 | 
			
		||||
        .crossfade(true)
 | 
			
		||||
        .size(Size.ORIGINAL)
 | 
			
		||||
        .build())
 | 
			
		||||
 | 
			
		||||
    Image(
 | 
			
		||||
        painter = painter,
 | 
			
		||||
        contentDescription = contentDescription,
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun NetworkSvgImage(
 | 
			
		||||
    url: String,
 | 
			
		||||
    contentDescription: String?,
 | 
			
		||||
    modifier: Modifier = Modifier
 | 
			
		||||
) {
 | 
			
		||||
    val imageLoader = ImageLoader.Builder(LocalContext.current)
 | 
			
		||||
        .components {
 | 
			
		||||
            add(SvgDecoder.Factory())
 | 
			
		||||
        }
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    val painter = rememberAsyncImagePainter(
 | 
			
		||||
        model = ImageRequest.Builder(LocalContext.current)
 | 
			
		||||
            .data(url)
 | 
			
		||||
            .size(Size.ORIGINAL)
 | 
			
		||||
            .build(),
 | 
			
		||||
        imageLoader = imageLoader
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    Image(
 | 
			
		||||
        painter = painter,
 | 
			
		||||
        contentDescription = contentDescription,
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun AboutPagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) {
 | 
			
		||||
    AboutPage(modifier, previewData = data)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,411 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.pages.subPages
 | 
			
		||||
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.sizeIn
 | 
			
		||||
import androidx.compose.foundation.lazy.LazyColumn
 | 
			
		||||
import androidx.compose.foundation.text.KeyboardOptions
 | 
			
		||||
import androidx.compose.material3.HorizontalDivider
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.text.input.KeyboardType
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
import androidx.lifecycle.viewmodel.compose.viewModel
 | 
			
		||||
import io.github.chinosk.gakumas.localify.MainActivity
 | 
			
		||||
import io.github.chinosk.gakumas.localify.R
 | 
			
		||||
import io.github.chinosk.gakumas.localify.getConfigState
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.BreastCollapsibleBoxViewModel
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.BreastCollapsibleBoxViewModelFactory
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuButton
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuTextInput
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun AdvanceSettingsPage(modifier: Modifier = Modifier,
 | 
			
		||||
             context: MainActivity? = null,
 | 
			
		||||
             previewData: GakumasConfig? = null,
 | 
			
		||||
             bottomSpacerHeight: Dp = 120.dp,
 | 
			
		||||
             screenH: Dp = 1080.dp) {
 | 
			
		||||
    val config = getConfigState(context, previewData)
 | 
			
		||||
    // val scrollState = rememberScrollState()
 | 
			
		||||
 | 
			
		||||
    val breastParamViewModel: BreastCollapsibleBoxViewModel =
 | 
			
		||||
        viewModel(factory = BreastCollapsibleBoxViewModelFactory(initiallyExpanded = false))
 | 
			
		||||
    val keyBoardOptionsDecimal = remember {
 | 
			
		||||
        KeyboardOptions(keyboardType = KeyboardType.Decimal)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LazyColumn(modifier = modifier
 | 
			
		||||
        .sizeIn(maxHeight = screenH)
 | 
			
		||||
        // .fillMaxHeight()
 | 
			
		||||
        // .verticalScroll(scrollState)
 | 
			
		||||
        .fillMaxWidth(),
 | 
			
		||||
        horizontalAlignment = Alignment.CenterHorizontally
 | 
			
		||||
    ) {
 | 
			
		||||
        item {
 | 
			
		||||
            GakuGroupBox(modifier, stringResource(R.string.camera_settings)) {
 | 
			
		||||
                Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
 | 
			
		||||
                    GakuSwitch(modifier, stringResource(R.string.enable_free_camera), checked = config.value.enableFreeCamera) {
 | 
			
		||||
                            v -> context?.onEnableFreeCameraChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer(Modifier.height(6.dp))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            GakuGroupBox(modifier, stringResource(R.string.debug_settings)) {
 | 
			
		||||
                Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
 | 
			
		||||
                    GakuSwitch(modifier, stringResource(R.string.useMasterDBTrans), checked = config.value.useMasterTrans) {
 | 
			
		||||
                            v -> context?.onUseMasterTransChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    GakuSwitch(modifier, stringResource(R.string.text_hook_test_mode), checked = config.value.textTest) {
 | 
			
		||||
                            v -> context?.onTextTestChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    GakuSwitch(modifier, stringResource(R.string.export_text), checked = config.value.dumpText) {
 | 
			
		||||
                            v -> context?.onDumpTextChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) {
 | 
			
		||||
                            v -> context?.onForceExportResourceChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    GakuSwitch(modifier, stringResource(R.string.login_as_ios), checked = config.value.loginAsIOS) {
 | 
			
		||||
                            v -> context?.onLoginAsIOSChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer(Modifier.height(6.dp))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            GakuGroupBox(modifier, stringResource(R.string.breast_param),
 | 
			
		||||
                contentPadding = 0.dp,
 | 
			
		||||
                onHeadClick = {
 | 
			
		||||
                    breastParamViewModel.expanded = !breastParamViewModel.expanded
 | 
			
		||||
                }) {
 | 
			
		||||
                CollapsibleBox(modifier = modifier,
 | 
			
		||||
                    viewModel = breastParamViewModel
 | 
			
		||||
                ) {
 | 
			
		||||
                    LazyColumn(modifier = modifier
 | 
			
		||||
                        .padding(8.dp)
 | 
			
		||||
                        .sizeIn(maxHeight = screenH),
 | 
			
		||||
                        verticalArrangement = Arrangement.spacedBy(12.dp)
 | 
			
		||||
                    ) {
 | 
			
		||||
                        item {
 | 
			
		||||
                            GakuSwitch(modifier = modifier,
 | 
			
		||||
                                checked = config.value.enableBreastParam,
 | 
			
		||||
                                text = stringResource(R.string.enable_breast_param)
 | 
			
		||||
                            ) { v -> context?.onEnableBreastParamChanged(v) }
 | 
			
		||||
                        }
 | 
			
		||||
                        item {
 | 
			
		||||
                            Row(modifier = modifier.fillMaxWidth(),
 | 
			
		||||
                                horizontalArrangement = Arrangement.spacedBy(2.dp)) {
 | 
			
		||||
                                val buttonModifier = remember {
 | 
			
		||||
                                    modifier
 | 
			
		||||
                                        .height(40.dp)
 | 
			
		||||
                                        .weight(1f)
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                GakuButton(modifier = buttonModifier,
 | 
			
		||||
                                    text = "??", onClick = { context?.onBClickPresetChanged(5) })
 | 
			
		||||
 | 
			
		||||
                                GakuButton(modifier = buttonModifier,
 | 
			
		||||
                                    text = "+5", onClick = { context?.onBClickPresetChanged(4) })
 | 
			
		||||
 | 
			
		||||
                                GakuButton(modifier = buttonModifier,
 | 
			
		||||
                                    text = "+4", onClick = { context?.onBClickPresetChanged(3) })
 | 
			
		||||
 | 
			
		||||
                                GakuButton(modifier = buttonModifier,
 | 
			
		||||
                                    text = "+3", onClick = { context?.onBClickPresetChanged(2) })
 | 
			
		||||
 | 
			
		||||
                                GakuButton(modifier = buttonModifier,
 | 
			
		||||
                                    text = "+2", onClick = { context?.onBClickPresetChanged(1) })
 | 
			
		||||
 | 
			
		||||
                                GakuButton(modifier = buttonModifier,
 | 
			
		||||
                                    text = "+1", onClick = { context?.onBClickPresetChanged(0) })
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            Row(modifier = modifier,
 | 
			
		||||
                                horizontalArrangement = Arrangement.spacedBy(4.dp)) {
 | 
			
		||||
                                GakuTextInput(modifier = modifier
 | 
			
		||||
                                    .height(45.dp)
 | 
			
		||||
                                    .weight(1f),
 | 
			
		||||
                                    fontSize = 14f,
 | 
			
		||||
                                    value = config.value.bDamping.toString(),
 | 
			
		||||
                                    onValueChange = { c -> context?.onBDampingChanged(c, 0, 0, 0)},
 | 
			
		||||
                                    label = { Text(stringResource(R.string.damping)) },
 | 
			
		||||
                                    keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                )
 | 
			
		||||
 | 
			
		||||
                                GakuTextInput(modifier = modifier
 | 
			
		||||
                                    .height(45.dp)
 | 
			
		||||
                                    .weight(1f),
 | 
			
		||||
                                    fontSize = 14f,
 | 
			
		||||
                                    value = config.value.bStiffness.toString(),
 | 
			
		||||
                                    onValueChange = { c -> context?.onBStiffnessChanged(c, 0, 0, 0)},
 | 
			
		||||
                                    label = { Text(stringResource(R.string.stiffness)) },
 | 
			
		||||
                                    keyboardOptions = keyBoardOptionsDecimal)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            Row(modifier = modifier,
 | 
			
		||||
                                horizontalArrangement = Arrangement.spacedBy(4.dp)) {
 | 
			
		||||
                                GakuTextInput(modifier = modifier
 | 
			
		||||
                                    .height(45.dp)
 | 
			
		||||
                                    .weight(1f),
 | 
			
		||||
                                    fontSize = 14f,
 | 
			
		||||
                                    value = config.value.bSpring.toString(),
 | 
			
		||||
                                    onValueChange = { c -> context?.onBSpringChanged(c, 0, 0, 0)},
 | 
			
		||||
                                    label = { Text(stringResource(R.string.spring)) },
 | 
			
		||||
                                    keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                )
 | 
			
		||||
 | 
			
		||||
                                GakuTextInput(modifier = modifier
 | 
			
		||||
                                    .height(45.dp)
 | 
			
		||||
                                    .weight(1f),
 | 
			
		||||
                                    fontSize = 14f,
 | 
			
		||||
                                    value = config.value.bPendulum.toString(),
 | 
			
		||||
                                    onValueChange = { c -> context?.onBPendulumChanged(c, 0, 0, 0)},
 | 
			
		||||
                                    label = { Text(stringResource(R.string.pendulum)) },
 | 
			
		||||
                                    keyboardOptions = keyBoardOptionsDecimal)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            Row(modifier = modifier,
 | 
			
		||||
                                horizontalArrangement = Arrangement.spacedBy(4.dp)) {
 | 
			
		||||
                                GakuTextInput(modifier = modifier
 | 
			
		||||
                                    .height(45.dp)
 | 
			
		||||
                                    .weight(1f),
 | 
			
		||||
                                    fontSize = 14f,
 | 
			
		||||
                                    value = config.value.bPendulumRange.toString(),
 | 
			
		||||
                                    onValueChange = { c -> context?.onBPendulumRangeChanged(c, 0, 0, 0)},
 | 
			
		||||
                                    label = { Text(stringResource(R.string.pendulumrange)) },
 | 
			
		||||
                                    keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                )
 | 
			
		||||
 | 
			
		||||
                                GakuTextInput(modifier = modifier
 | 
			
		||||
                                    .height(45.dp)
 | 
			
		||||
                                    .weight(1f),
 | 
			
		||||
                                    fontSize = 14f,
 | 
			
		||||
                                    value = config.value.bAverage.toString(),
 | 
			
		||||
                                    onValueChange = { c -> context?.onBAverageChanged(c, 0, 0, 0)},
 | 
			
		||||
                                    label = { Text(stringResource(R.string.average)) },
 | 
			
		||||
                                    keyboardOptions = keyBoardOptionsDecimal)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            GakuTextInput(modifier = modifier
 | 
			
		||||
                                .height(45.dp)
 | 
			
		||||
                                .fillMaxWidth(),
 | 
			
		||||
                                fontSize = 14f,
 | 
			
		||||
                                value = config.value.bRootWeight.toString(),
 | 
			
		||||
                                onValueChange = { c -> context?.onBRootWeightChanged(c, 0, 0, 0)},
 | 
			
		||||
                                label = { Text(stringResource(R.string.rootweight)) },
 | 
			
		||||
                                keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            GakuSwitch(modifier = modifier,
 | 
			
		||||
                                checked = config.value.bUseScale,
 | 
			
		||||
                                leftPart = {
 | 
			
		||||
                                    GakuTextInput(modifier = modifier
 | 
			
		||||
                                        .height(45.dp),
 | 
			
		||||
                                        fontSize = 14f,
 | 
			
		||||
                                        value = config.value.bScale.toString(),
 | 
			
		||||
                                        onValueChange = { c -> context?.onBScaleChanged(c, 0, 0, 0)},
 | 
			
		||||
                                        label = { Text(stringResource(R.string.breast_scale)) },
 | 
			
		||||
                                        keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            ) { v -> context?.onBUseScaleChanged(v) }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            GakuSwitch(modifier = modifier,
 | 
			
		||||
                                checked = config.value.bUseArmCorrection,
 | 
			
		||||
                                text = stringResource(R.string.usearmcorrection)
 | 
			
		||||
                            ) { v -> context?.onBUseArmCorrectionChanged(v) }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            HorizontalDivider(
 | 
			
		||||
                                thickness = 1.dp,
 | 
			
		||||
                                color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            GakuSwitch(modifier = modifier,
 | 
			
		||||
                                checked = config.value.bUseLimit,
 | 
			
		||||
                                text = stringResource(R.string.uselimit_0_1)
 | 
			
		||||
                            ) { v ->
 | 
			
		||||
                                context?.onBUseLimitChanged(v)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        item {
 | 
			
		||||
                            CollapsibleBox(modifier = modifier,
 | 
			
		||||
                                expandState = config.value.bUseLimit,
 | 
			
		||||
                                collapsedHeight = 0.dp,
 | 
			
		||||
                                showExpand = false
 | 
			
		||||
                            ){
 | 
			
		||||
                                Row(modifier = modifier,
 | 
			
		||||
                                    horizontalArrangement = Arrangement.spacedBy(4.dp)) {
 | 
			
		||||
                                    val textInputModifier = remember {
 | 
			
		||||
                                        modifier
 | 
			
		||||
                                            .height(45.dp)
 | 
			
		||||
                                            .weight(1f)
 | 
			
		||||
                                    }
 | 
			
		||||
 | 
			
		||||
                                    GakuTextInput(modifier = textInputModifier,
 | 
			
		||||
                                        fontSize = 14f,
 | 
			
		||||
                                        value = config.value.bLimitXx.toString(),
 | 
			
		||||
                                        onValueChange = { c -> context?.onBLimitXxChanged(c, 0, 0, 0)},
 | 
			
		||||
                                        label = { Text(stringResource(R.string.axisx_x)) },
 | 
			
		||||
                                        keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                    )
 | 
			
		||||
 | 
			
		||||
                                    GakuTextInput(modifier = textInputModifier,
 | 
			
		||||
                                        fontSize = 14f,
 | 
			
		||||
                                        value = config.value.bLimitYx.toString(),
 | 
			
		||||
                                        onValueChange = { c -> context?.onBLimitYxChanged(c, 0, 0, 0)},
 | 
			
		||||
                                        label = { Text(stringResource(R.string.axisy_x)) },
 | 
			
		||||
                                        keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                    )
 | 
			
		||||
 | 
			
		||||
                                    GakuTextInput(modifier = textInputModifier,
 | 
			
		||||
                                        fontSize = 14f,
 | 
			
		||||
                                        value = config.value.bLimitZx.toString(),
 | 
			
		||||
                                        onValueChange = { c -> context?.onBLimitZxChanged(c, 0, 0, 0)},
 | 
			
		||||
                                        label = { Text(stringResource(R.string.axisz_x)) },
 | 
			
		||||
                                        keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                Row(modifier = modifier,
 | 
			
		||||
                                    horizontalArrangement = Arrangement.spacedBy(4.dp)) {
 | 
			
		||||
                                    val textInputModifier = remember {
 | 
			
		||||
                                        modifier
 | 
			
		||||
                                            .height(45.dp)
 | 
			
		||||
                                            .weight(1f)
 | 
			
		||||
                                    }
 | 
			
		||||
 | 
			
		||||
                                    GakuTextInput(modifier = textInputModifier,
 | 
			
		||||
                                        fontSize = 14f,
 | 
			
		||||
                                        value = config.value.bLimitXy.toString(),
 | 
			
		||||
                                        onValueChange = { c -> context?.onBLimitXyChanged(c, 0, 0, 0)},
 | 
			
		||||
                                        label = { Text(stringResource(R.string.axisx_y)) },
 | 
			
		||||
                                        keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                    )
 | 
			
		||||
 | 
			
		||||
                                    GakuTextInput(modifier = textInputModifier,
 | 
			
		||||
                                        fontSize = 14f,
 | 
			
		||||
                                        value = config.value.bLimitYy.toString(),
 | 
			
		||||
                                        onValueChange = { c -> context?.onBLimitYyChanged(c, 0, 0, 0)},
 | 
			
		||||
                                        label = { Text(stringResource(R.string.axisy_y)) },
 | 
			
		||||
                                        keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                    )
 | 
			
		||||
 | 
			
		||||
                                    GakuTextInput(modifier = textInputModifier,
 | 
			
		||||
                                        fontSize = 14f,
 | 
			
		||||
                                        value = config.value.bLimitZy.toString(),
 | 
			
		||||
                                        onValueChange = { c -> context?.onBLimitZyChanged(c, 0, 0, 0)},
 | 
			
		||||
                                        label = { Text(stringResource(R.string.axisz_y)) },
 | 
			
		||||
                                        keyboardOptions = keyBoardOptionsDecimal
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            if (config.value.dbgMode) {
 | 
			
		||||
                Spacer(Modifier.height(6.dp))
 | 
			
		||||
 | 
			
		||||
                GakuGroupBox(modifier, stringResource(R.string.test_mode_live)) {
 | 
			
		||||
                    Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
 | 
			
		||||
                        GakuSwitch(modifier, stringResource(R.string.unlockAllLive),
 | 
			
		||||
                            checked = config.value.unlockAllLive) {
 | 
			
		||||
                                v -> context?.onUnlockAllLiveChanged(v)
 | 
			
		||||
                        }
 | 
			
		||||
                        GakuSwitch(modifier, stringResource(R.string.unlockAllLiveCostume),
 | 
			
		||||
                            checked = config.value.unlockAllLiveCostume) {
 | 
			
		||||
                                v -> context?.onUnlockAllLiveCostumeChanged(v)
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        /*
 | 
			
		||||
                        HorizontalDivider(
 | 
			
		||||
                            thickness = 1.dp,
 | 
			
		||||
                            color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
 | 
			
		||||
                        )
 | 
			
		||||
                        GakuSwitch(modifier, stringResource(R.string.liveUseCustomeDress),
 | 
			
		||||
                            checked = config.value.enableLiveCustomeDress) {
 | 
			
		||||
                                v -> context?.onLiveCustomeDressChanged(v)
 | 
			
		||||
                        }
 | 
			
		||||
                        GakuTextInput(modifier = modifier
 | 
			
		||||
                            .height(45.dp)
 | 
			
		||||
                            .fillMaxWidth(),
 | 
			
		||||
                            fontSize = 14f,
 | 
			
		||||
                            value = config.value.liveCustomeHeadId,
 | 
			
		||||
                            onValueChange = { c -> context?.onLiveCustomeHeadIdChanged(c, 0, 0, 0)},
 | 
			
		||||
                            label = { Text(stringResource(R.string.live_costume_head_id),
 | 
			
		||||
                                fontSize = 12.sp) }
 | 
			
		||||
                        )
 | 
			
		||||
                        GakuTextInput(modifier = modifier
 | 
			
		||||
                            .height(45.dp)
 | 
			
		||||
                            .fillMaxWidth(),
 | 
			
		||||
                            fontSize = 14f,
 | 
			
		||||
                            value = config.value.liveCustomeCostumeId,
 | 
			
		||||
                            onValueChange = { c -> context?.onLiveCustomeCostumeIdChanged(c, 0, 0, 0)},
 | 
			
		||||
                            label = { Text(stringResource(R.string.live_custome_dress_id)) }
 | 
			
		||||
                        )*/
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            Spacer(modifier = modifier.height(bottomSpacerHeight))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Composable
 | 
			
		||||
fun AdvanceSettingsPagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) {
 | 
			
		||||
    AdvanceSettingsPage(modifier, previewData = data)
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue