@airisu-dev/react-native-live2d-skia 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/Live2dSkia.podspec +159 -0
  3. package/README.md +258 -2
  4. package/android/CMakeLists.txt +113 -0
  5. package/android/build.gradle +223 -0
  6. package/android/fix-prefab.gradle +51 -0
  7. package/android/gradle.properties +5 -0
  8. package/android/src/main/AndroidManifest.xml +2 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +9 -0
  10. package/android/src/main/java/com/live2dskia/Live2dSkiaPackage.kt +20 -0
  11. package/app.plugin.js +2 -0
  12. package/cli/build/index.js +157 -0
  13. package/cpp/cubism/CubismCompat.hpp +26 -0
  14. package/cpp/cubism/CubismHelpers.hpp +27 -0
  15. package/cpp/cubism/CubismRendererStub.cpp +14 -0
  16. package/cpp/cubism/CubismSetup.cpp +54 -0
  17. package/cpp/cubism/CubismSetup.hpp +9 -0
  18. package/cpp/cubism/HeldParameterUpdater.hpp +25 -0
  19. package/cpp/cubism/Live2DUserModel.cpp +125 -0
  20. package/cpp/cubism/Live2DUserModel.hpp +57 -0
  21. package/cpp/cubism/Live2DUserModelAnimation.cpp +107 -0
  22. package/cpp/drawing/JsiUnwrap.hpp +25 -0
  23. package/cpp/drawing/MeshCache.cpp +75 -0
  24. package/cpp/drawing/MeshCache.hpp +45 -0
  25. package/cpp/drawing/SkiaDrawer.cpp +138 -0
  26. package/cpp/drawing/SkiaDrawer.hpp +42 -0
  27. package/cpp/hybrid/HybridLive2DModel.cpp +174 -0
  28. package/cpp/hybrid/HybridLive2DModel.hpp +64 -0
  29. package/cpp/hybrid/HybridLive2dSkia.cpp +10 -0
  30. package/cpp/hybrid/HybridLive2dSkia.hpp +11 -0
  31. package/ios/Bridge.h +8 -0
  32. package/nitro.json +20 -0
  33. package/nitrogen/generated/.gitattributes +1 -0
  34. package/nitrogen/generated/android/Live2dSkia+autolinking.cmake +82 -0
  35. package/nitrogen/generated/android/Live2dSkia+autolinking.gradle +27 -0
  36. package/nitrogen/generated/android/Live2dSkiaOnLoad.cpp +49 -0
  37. package/nitrogen/generated/android/Live2dSkiaOnLoad.hpp +34 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/live2dskia/Live2dSkiaOnLoad.kt +35 -0
  39. package/nitrogen/generated/ios/Live2dSkia+autolinking.rb +62 -0
  40. package/nitrogen/generated/ios/Live2dSkia-Swift-Cxx-Bridge.cpp +17 -0
  41. package/nitrogen/generated/ios/Live2dSkia-Swift-Cxx-Bridge.hpp +27 -0
  42. package/nitrogen/generated/ios/Live2dSkia-Swift-Cxx-Umbrella.hpp +38 -0
  43. package/nitrogen/generated/ios/Live2dSkiaAutolinking.mm +35 -0
  44. package/nitrogen/generated/ios/Live2dSkiaAutolinking.swift +16 -0
  45. package/nitrogen/generated/shared/c++/DrawableData.hpp +116 -0
  46. package/nitrogen/generated/shared/c++/HybridLive2DModelSpec.cpp +39 -0
  47. package/nitrogen/generated/shared/c++/HybridLive2DModelSpec.hpp +86 -0
  48. package/nitrogen/generated/shared/c++/HybridLive2dSkiaSpec.cpp +21 -0
  49. package/nitrogen/generated/shared/c++/HybridLive2dSkiaSpec.hpp +66 -0
  50. package/nitrogen/generated/shared/c++/MotionPriority.hpp +80 -0
  51. package/package.json +149 -4
  52. package/plugin/build/index.d.ts +14 -0
  53. package/plugin/build/index.js +81 -0
  54. package/src/components/Live2D.tsx +131 -0
  55. package/src/components/index.ts +2 -0
  56. package/src/hooks/index.ts +8 -0
  57. package/src/hooks/useLive2DGesture.ts +140 -0
  58. package/src/hooks/useLive2DModel.ts +47 -0
  59. package/src/index.ts +5 -0
  60. package/src/loader/cubismJson.ts +23 -0
  61. package/src/loader/index.ts +9 -0
  62. package/src/loader/loadModel.ts +195 -0
  63. package/src/loader/modelContext.ts +80 -0
  64. package/src/loader/modelUrl.ts +112 -0
  65. package/src/native.ts +4 -0
  66. package/src/rendering/decodeImage.ts +12 -0
  67. package/src/rendering/drawer.ts +256 -0
  68. package/src/rendering/skia.ts +26 -0
  69. package/src/rendering/skiaTypes.ts +99 -0
  70. package/src/rendering/ticker/addToTicker.ts +52 -0
  71. package/src/rendering/ticker/constants.ts +11 -0
  72. package/src/rendering/ticker/frames.ts +35 -0
  73. package/src/rendering/ticker/index.ts +5 -0
  74. package/src/rendering/ticker/loop.ts +115 -0
  75. package/src/rendering/ticker/state.ts +65 -0
  76. package/src/rendering/ticker/stats.ts +36 -0
  77. package/src/rendering/ticker/types.ts +57 -0
  78. package/src/specs/react-native-live2d-skia.nitro.ts +88 -0
  79. package/src/utils/clock.ts +10 -0
@@ -0,0 +1,223 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+
7
+ dependencies {
8
+ classpath "com.android.tools.build:gradle:8.8.0"
9
+ }
10
+ }
11
+
12
+ def reactNativeArchitectures() {
13
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
14
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
15
+ }
16
+
17
+ def isNewArchitectureEnabled() {
18
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19
+ }
20
+
21
+ apply plugin: "com.android.library"
22
+ apply plugin: 'org.jetbrains.kotlin.android'
23
+ apply from: '../nitrogen/generated/android/Live2dSkia+autolinking.gradle'
24
+ apply from: "./fix-prefab.gradle"
25
+
26
+ if (isNewArchitectureEnabled()) {
27
+ apply plugin: "com.facebook.react"
28
+ }
29
+
30
+ // Cubism SDK for Native: proprietary, so each app downloads it from Live2D and
31
+ // sets its folder (app.plugin.js writes live2dSkia.cubismSdkPath, relative to android/).
32
+ def findCubismSdk() {
33
+ def configured = System.getenv("LIVE2D_SKIA_CUBISM_SDK") ?: rootProject.findProperty("live2dSkia.cubismSdkPath")
34
+ if (!configured) {
35
+ throw new GradleException("[react-native-live2d-skia] Cubism SDK for Native isn't configured. Add the plugin to app.json: " +
36
+ "[\"@airisu-dev/react-native-live2d-skia\", { \"cubismSdkPath\": \"./vendor/CubismSdkForNative\" }], download the SDK with `@airisu-dev/react-native-live2d-skia fetch-sdk --accept-license` (through npx, bunx, yarn or pnpm), then prebuild.")
37
+ }
38
+ def dir = rootProject.file(configured)
39
+ def info = new File(dir, "cubism-info.yml")
40
+ if (!info.exists()) {
41
+ throw new GradleException("[react-native-live2d-skia] Cubism SDK for Native not found at ${dir} (no cubism-info.yml). " +
42
+ "Download it with `@airisu-dev/react-native-live2d-skia fetch-sdk --accept-license` (through npx, bunx, yarn or pnpm).")
43
+ }
44
+ def matcher = info.text =~ /(?m)^version:\s*(\S+)/
45
+ def version = matcher.find() ? matcher.group(1) : "unknown"
46
+ checkCubismSdkVersion(version, dir)
47
+ return [dir: dir.absolutePath.replace("\\", "/"), version: version]
48
+ }
49
+
50
+ // A Cubism SDK version as a number: major * 10000 + release * 100 + patch, so
51
+ // 5-r.4.1 is 50401 (a -beta suffix is ignored). Null if it isn't one.
52
+ def cubismSdkVersionNumber(String version) {
53
+ def m = version =~ /^(\d+)-r\.(\d+)(?:\.(\d+))?/
54
+ return m.find() ? m.group(1).toInteger() * 10000 + m.group(2).toInteger() * 100 + (m.group(3) ?: "0").toInteger() : null
55
+ }
56
+
57
+ // Listed versions (cubismSdk.versions in package.json) are supported. A newer
58
+ // release of a listed major only warns; anything else fails.
59
+ def checkCubismSdkVersion(String version, File dir) {
60
+ def listed = new groovy.json.JsonSlurper().parse(file("../package.json")).cubismSdk.versions.keySet()
61
+ if (listed.contains(version)) return
62
+ def number = cubismSdkVersionNumber(version)
63
+ def newestOfMajor = number == null ? null : listed.collect { cubismSdkVersionNumber(it) }
64
+ .findAll { it.intdiv(10000) == number.intdiv(10000) }.max()
65
+ if (newestOfMajor == null || number < newestOfMajor) {
66
+ throw new GradleException("[react-native-live2d-skia] Cubism SDK for Native ${version} at ${dir} isn't supported: use ${listed.join(', ')}.")
67
+ }
68
+ logger.warn("[react-native-live2d-skia] Cubism SDK for Native ${version} found, tested with ${listed.join(', ')}.")
69
+ }
70
+
71
+ // The folder of a node package, as `from` resolves it. Null if it isn't installed.
72
+ def resolveNodePackage(String name, File from) {
73
+ def process = ["node", "-p",
74
+ "require('path').dirname(require.resolve(process.argv[1] + '/package.json', { paths: [process.argv[2]] }))",
75
+ name, from.absolutePath].execute()
76
+ def out = process.text.trim()
77
+ return process.waitFor() == 0 && out ? new File(out) : null
78
+ }
79
+
80
+ // Drawing in C++ onto Skia canvases (cpp/drawing/SkiaDrawer.cpp) needs the headers,
81
+ // defines and prebuilt libskia.a of the Skia the app uses: react-native-skia
82
+ // (v3, Graphite) or @shopify/react-native-skia (v2, Ganesh), as app.plugin.js
83
+ // found in the app's package.json (live2dSkia.skiaPackage). Resolved from the
84
+ // app, since a monorepo can have both installed. No arguments: drawn in JS.
85
+ def skiaCmakeArguments() {
86
+ def name = rootProject.findProperty("live2dSkia.skiaPackage")
87
+ if (!name) return []
88
+ def appDir = rootProject.projectDir.parentFile
89
+ def skiaDir = resolveNodePackage(name, appDir)
90
+ def graphite = name == "react-native-skia"
91
+ def libsDir = skiaDir ? resolveNodePackage(graphite ? "react-native-skia-graphite-android" : "react-native-skia-android", skiaDir) : null
92
+ def reactNativeDir = resolveNodePackage("react-native", appDir)
93
+ if (!skiaDir || !libsDir || !reactNativeDir) {
94
+ logger.warn("[react-native-live2d-skia] ${name} or its Android binaries not found from ${appDir}: drawing in JS.")
95
+ return []
96
+ }
97
+ return ["-DRN_SKIA_DIR=${skiaDir.absolutePath.replace("\\", "/")}",
98
+ "-DRN_SKIA_LIBS=${new File(libsDir, "libs").absolutePath.replace("\\", "/")}",
99
+ "-DRN_SKIA_GRAPHITE=${graphite ? "ON" : "OFF"}",
100
+ "-DREACT_NATIVE_DIR=${reactNativeDir.absolutePath.replace("\\", "/")}"]
101
+ }
102
+
103
+ def getExtOrDefault(name) {
104
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Live2dSkia_" + name]
105
+ }
106
+
107
+ def getExtOrIntegerDefault(name) {
108
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Live2dSkia_" + name]).toInteger()
109
+ }
110
+
111
+ android {
112
+ namespace "com.live2dskia"
113
+
114
+ ndkVersion getExtOrDefault("ndkVersion")
115
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
116
+
117
+ defaultConfig {
118
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
119
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
120
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
121
+
122
+ externalNativeBuild {
123
+ cmake {
124
+ cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
125
+ def cubismSdk = findCubismSdk()
126
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
127
+ "-DCUBISM_SDK_DIR=${cubismSdk.dir}",
128
+ "-DCUBISM_SDK_VERSION_NUMBER=${cubismSdkVersionNumber(cubismSdk.version)}", *skiaCmakeArguments()
129
+ abiFilters (*reactNativeArchitectures())
130
+
131
+ buildTypes {
132
+ debug {
133
+ cppFlags "-O1 -g"
134
+ }
135
+ release {
136
+ cppFlags "-O2"
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ externalNativeBuild {
144
+ cmake {
145
+ path "CMakeLists.txt"
146
+ }
147
+ }
148
+
149
+ packagingOptions {
150
+ excludes = [
151
+ "META-INF",
152
+ "META-INF/**",
153
+ "**/libc++_shared.so",
154
+ "**/libfbjni.so",
155
+ "**/libjsi.so",
156
+ "**/libfolly_json.so",
157
+ "**/libfolly_runtime.so",
158
+ "**/libglog.so",
159
+ "**/libhermes.so",
160
+ "**/libhermes-executor-debug.so",
161
+ "**/libhermes_executor.so",
162
+ "**/libreactnative.so",
163
+ "**/libreactnativejni.so",
164
+ "**/libturbomodulejsijni.so",
165
+ "**/libreact_nativemodule_core.so",
166
+ "**/libjscexecutor.so"
167
+ ]
168
+ }
169
+
170
+ buildFeatures {
171
+ buildConfig true
172
+ prefab true
173
+ }
174
+
175
+ buildTypes {
176
+ release {
177
+ minifyEnabled false
178
+ }
179
+ }
180
+
181
+ lintOptions {
182
+ disable "GradleCompatible"
183
+ }
184
+
185
+ compileOptions {
186
+ sourceCompatibility JavaVersion.VERSION_1_8
187
+ targetCompatibility JavaVersion.VERSION_1_8
188
+ }
189
+
190
+ sourceSets {
191
+ main {
192
+ if (isNewArchitectureEnabled()) {
193
+ java.srcDirs += [
194
+ // React Codegen files
195
+ "${project.buildDir}/generated/source/codegen/java"
196
+ ]
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ repositories {
203
+ mavenCentral()
204
+ google()
205
+ }
206
+
207
+
208
+ dependencies {
209
+ // For < 0.71, this will be from the local maven repo
210
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
211
+ //noinspection GradleDynamicVersion
212
+ implementation "com.facebook.react:react-native:+"
213
+
214
+ implementation project(":react-native-nitro-modules")
215
+ }
216
+
217
+ if (isNewArchitectureEnabled()) {
218
+ react {
219
+ jsRootDir = file("../src/")
220
+ libraryName = "Live2dSkia"
221
+ codegenJavaPackageName = "com.live2dskia"
222
+ }
223
+ }
@@ -0,0 +1,51 @@
1
+ tasks.configureEach { task ->
2
+ // Make sure that we generate our prefab publication file only after having built the native library
3
+ // so that not a header publication file, but a full configuration publication will be generated, which
4
+ // will include the .so file
5
+
6
+ def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/
7
+ def matcher = task.name =~ prefabConfigurePattern
8
+ if (matcher.matches()) {
9
+ def variantName = matcher[0][1]
10
+ task.outputs.upToDateWhen { false }
11
+ task.dependsOn("externalNativeBuild${variantName}")
12
+ }
13
+ }
14
+
15
+ afterEvaluate {
16
+ def abis = reactNativeArchitectures()
17
+ rootProject.allprojects.each { proj ->
18
+ if (proj === rootProject) return
19
+
20
+ def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config ->
21
+ config.dependencies.any { dep ->
22
+ dep.group == project.group && dep.name == project.name
23
+ }
24
+ }
25
+ if (!dependsOnThisLib && proj != project) return
26
+
27
+ if (!proj.plugins.hasPlugin('com.android.application') && !proj.plugins.hasPlugin('com.android.library')) {
28
+ return
29
+ }
30
+
31
+ def variants = proj.android.hasProperty('applicationVariants') ? proj.android.applicationVariants : proj.android.libraryVariants
32
+ // Touch the prefab_config.json files to ensure that in ExternalNativeJsonGenerator.kt we will re-trigger the prefab CLI to
33
+ // generate a libnameConfig.cmake file that will contain our native library (.so).
34
+ // See this condition: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/ExternalNativeJsonGenerator.kt;l=207-219?q=createPrefabBuildSystemGlue
35
+ variants.all { variant ->
36
+ def variantName = variant.name
37
+ abis.each { abi ->
38
+ def searchDir = new File(proj.projectDir, ".cxx/${variantName}")
39
+ if (!searchDir.exists()) return
40
+ def matches = []
41
+ searchDir.eachDir { randomDir ->
42
+ def prefabFile = new File(randomDir, "${abi}/prefab_config.json")
43
+ if (prefabFile.exists()) matches << prefabFile
44
+ }
45
+ matches.each { prefabConfig ->
46
+ prefabConfig.setLastModified(System.currentTimeMillis())
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,5 @@
1
+ Live2dSkia_kotlinVersion=2.1.20
2
+ Live2dSkia_minSdkVersion=23
3
+ Live2dSkia_targetSdkVersion=35
4
+ Live2dSkia_compileSdkVersion=34
5
+ Live2dSkia_ndkVersion=27.1.12297006
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,9 @@
1
+ #include <jni.h>
2
+ #include <fbjni/fbjni.h>
3
+ #include "Live2dSkiaOnLoad.hpp"
4
+
5
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
6
+ return facebook::jni::initialize(vm, []() {
7
+ margelo::nitro::live2dskia::registerAllNatives();
8
+ });
9
+ }
@@ -0,0 +1,20 @@
1
+ package com.live2dskia;
2
+
3
+ import com.facebook.react.bridge.NativeModule;
4
+ import com.facebook.react.bridge.ReactApplicationContext;
5
+ import com.facebook.react.module.model.ReactModuleInfoProvider;
6
+ import com.facebook.react.BaseReactPackage;
7
+ import com.margelo.nitro.live2dskia.Live2dSkiaOnLoad;
8
+
9
+
10
+ public class Live2dSkiaPackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
12
+
13
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { emptyMap() }
14
+
15
+ companion object {
16
+ init {
17
+ Live2dSkiaOnLoad.initializeNative();
18
+ }
19
+ }
20
+ }
package/app.plugin.js ADDED
@@ -0,0 +1,2 @@
1
+ // Expo CLI looks for this file. The plugin is in plugin/src, built to plugin/build.
2
+ module.exports = require('./plugin/build').default
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.sha256 = void 0;
8
+ exports.parseArgs = parseArgs;
9
+ // @airisu-dev/react-native-live2d-skia fetch-sdk --accept-license [--sdk-version <version>] [--out <dir>] [--keep-zip]
10
+ //
11
+ // Downloads Cubism SDK for Native into <dir> (default ./vendor/CubismSdkForNative), for the
12
+ // config plugin's `cubismSdkPath`. The Core is proprietary and not shipped: downloading means accepting Live2D's license.
13
+ const child_process_1 = require("child_process");
14
+ const crypto_1 = __importDefault(require("crypto"));
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ // The SDK versions the package supports. Two levels up from both cli/src and cli/build.
18
+ const { cubismSdk } = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', '..', 'package.json'), 'utf8'));
19
+ const VERSIONS = Object.keys(cubismSdk.versions);
20
+ const sdkUrl = (version) => `https://cubism.live2d.com/sdk-native/bin/CubismSdkForNative-${version}.zip`;
21
+ const DEFAULT_OUT = 'vendor/CubismSdkForNative';
22
+ const LICENSES = [
23
+ 'Live2D Proprietary Software License (Cubism Core): https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html',
24
+ 'Live2D Open Software License (Cubism Native Framework): https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html',
25
+ 'Cubism SDK Release License (required to publish apps, for businesses above 10M JPY a year): https://www.live2d.com/en/sdk/license/',
26
+ ];
27
+ function usage() {
28
+ console.log(`Usage: @airisu-dev/react-native-live2d-skia fetch-sdk --accept-license [--sdk-version <version>] [--out <dir>] [--keep-zip]
29
+
30
+ Downloads Cubism SDK for Native from Live2D into <dir>
31
+ (default: ./${DEFAULT_OUT}). Then set it in app.json:
32
+
33
+ "plugins": [["@airisu-dev/react-native-live2d-skia", { "cubismSdkPath": "./${DEFAULT_OUT}" }]]
34
+
35
+ --accept-license Required: you accept Live2D's license terms:
36
+ ${LICENSES.map((l) => ` - ${l}`).join('\n')}
37
+ --sdk-version Which version: ${VERSIONS.join(', ')} (default: ${cubismSdk.latest}).
38
+ --out <dir> Where to extract the SDK.
39
+ --keep-zip Keep the downloaded zip next to <dir> (e.g. to cache it on CI).`);
40
+ }
41
+ function fail(message) {
42
+ console.error(`error: ${message}`);
43
+ process.exit(1);
44
+ }
45
+ function parseArgs(argv) {
46
+ const args = {
47
+ command: argv[0] === '--help' || argv[0] === '-h' ? 'help' : argv[0],
48
+ acceptLicense: false,
49
+ version: cubismSdk.latest,
50
+ out: DEFAULT_OUT,
51
+ keepZip: false,
52
+ };
53
+ for (let i = 1; i < argv.length; i++) {
54
+ const arg = argv[i];
55
+ const next = argv[i + 1];
56
+ if (arg === '--accept-license')
57
+ args.acceptLicense = true;
58
+ else if (arg === '--keep-zip')
59
+ args.keepZip = true;
60
+ else if (arg === '--sdk-version' && next) {
61
+ args.version = next;
62
+ i++;
63
+ }
64
+ else if (arg === '--out' && next) {
65
+ args.out = next;
66
+ i++;
67
+ }
68
+ else if (arg === '--help' || arg === '-h')
69
+ args.command = 'help';
70
+ else
71
+ throw new Error(`unknown option ${arg}`);
72
+ }
73
+ return args;
74
+ }
75
+ const sha256 = (file) => crypto_1.default.createHash('sha256').update(fs_1.default.readFileSync(file)).digest('hex');
76
+ exports.sha256 = sha256;
77
+ async function download(url, file) {
78
+ console.log(`Downloading ${url}`);
79
+ const response = await fetch(url);
80
+ if (!response.ok)
81
+ fail(`download failed: HTTP ${response.status}`);
82
+ fs_1.default.writeFileSync(file, Buffer.from(await response.arrayBuffer()));
83
+ }
84
+ // unzip where it exists (macOS, most Linux), else tar, which reads zips on
85
+ // Windows 10+ (bsdtar) and macOS.
86
+ function extract(zip, dir) {
87
+ const hasUnzip = !(0, child_process_1.spawnSync)('unzip', ['-v'], { stdio: 'ignore' }).error;
88
+ const [cmd, args] = hasUnzip
89
+ ? ['unzip', ['-q', zip, '-d', dir]]
90
+ : ['tar', ['-xf', zip, '-C', dir]];
91
+ const result = (0, child_process_1.spawnSync)(cmd, args, { stdio: 'inherit' });
92
+ if (result.error || result.status !== 0) {
93
+ fail(`couldn't extract ${zip} with ${cmd}`);
94
+ }
95
+ }
96
+ async function fetchSdk({ acceptLicense, version, out, keepZip }) {
97
+ if (!acceptLicense) {
98
+ console.error("Cubism SDK for Native is Live2D's software. Downloading it means accepting:\n" +
99
+ LICENSES.map((l) => ` - ${l}`).join('\n') +
100
+ '\nRe-run with --accept-license to accept them and download.');
101
+ process.exit(1);
102
+ }
103
+ const expected = cubismSdk.versions[version]?.sha256;
104
+ if (!expected)
105
+ fail(`Cubism SDK for Native ${version} isn't supported: use ${VERSIONS.join(', ')}`);
106
+ const sdkDir = path_1.default.resolve(out);
107
+ const versionFile = path_1.default.join(sdkDir, '.version');
108
+ if (fs_1.default.existsSync(versionFile) && fs_1.default.readFileSync(versionFile, 'utf8').trim() === version) {
109
+ console.log(`Cubism SDK for Native ${version} already in ${sdkDir}`);
110
+ return;
111
+ }
112
+ const zip = `${sdkDir}-${version}.zip`;
113
+ fs_1.default.mkdirSync(path_1.default.dirname(sdkDir), { recursive: true });
114
+ if (!fs_1.default.existsSync(zip) || (0, exports.sha256)(zip) !== expected) {
115
+ await download(sdkUrl(version), zip);
116
+ }
117
+ const actual = (0, exports.sha256)(zip);
118
+ if (actual !== expected) {
119
+ fs_1.default.rmSync(zip, { force: true });
120
+ fail(`checksum mismatch for ${zip} (expected ${expected}, got ${actual})`);
121
+ }
122
+ // Extract next to the destination, so the move is a rename on the same drive.
123
+ const tmp = `${sdkDir}.extracting`;
124
+ fs_1.default.rmSync(tmp, { recursive: true, force: true });
125
+ fs_1.default.mkdirSync(tmp);
126
+ try {
127
+ extract(zip, tmp);
128
+ fs_1.default.rmSync(sdkDir, { recursive: true, force: true });
129
+ fs_1.default.renameSync(path_1.default.join(tmp, `CubismSdkForNative-${version}`), sdkDir);
130
+ }
131
+ finally {
132
+ fs_1.default.rmSync(tmp, { recursive: true, force: true });
133
+ }
134
+ fs_1.default.writeFileSync(versionFile, `${version}\n`);
135
+ if (!keepZip)
136
+ fs_1.default.rmSync(zip, { force: true });
137
+ const relative = path_1.default.relative(process.cwd(), sdkDir).split(path_1.default.sep).join('/');
138
+ console.log(`Extracted Cubism SDK for Native ${version} to ${sdkDir}
139
+
140
+ Next:
141
+ - Keep it out of public repos: add "/${relative}/" to .gitignore.
142
+ - Set it in app.json: "plugins": [["@airisu-dev/react-native-live2d-skia", { "cubismSdkPath": "./${relative}" }]]
143
+ - On EAS Build, run this command in an "eas-build-post-install" script.`);
144
+ }
145
+ async function main() {
146
+ const args = parseArgs(process.argv.slice(2));
147
+ if (args.command === 'fetch-sdk')
148
+ await fetchSdk(args);
149
+ else {
150
+ usage();
151
+ if (args.command && args.command !== 'help')
152
+ process.exit(1);
153
+ }
154
+ }
155
+ if (require.main === module) {
156
+ main().catch((e) => fail(e instanceof Error ? e.message : String(e)));
157
+ }
@@ -0,0 +1,26 @@
1
+ #pragma once
2
+
3
+ // Differences between the Cubism SDK for Native versions react-native-live2d-skia builds
4
+ // against (cubismSdk.versions in package.json). build.gradle and the podspec
5
+ // pass the SDK's version as LIVE2D_SKIA_CUBISM_SDK: major * 10000 + release *
6
+ // 100 + patch, so 5-r.4.1 is 50401. Version-specific calls go here, behind
7
+ // #if on it (older SDK versions).
8
+
9
+ #include "Live2DCubismCore.hpp"
10
+
11
+ #ifndef LIVE2D_SKIA_CUBISM_SDK
12
+ #error "[react-native-live2d-skia] LIVE2D_SKIA_CUBISM_SDK isn't set: build through Gradle or CocoaPods."
13
+ #endif
14
+
15
+ #if LIVE2D_SKIA_CUBISM_SDK < 50500
16
+ #error "[react-native-live2d-skia] Cubism SDK for Native before 5-r.5 isn't supported."
17
+ #endif
18
+
19
+ namespace margelo::nitro::live2dskia::cubism {
20
+
21
+ // Drawable render orders. Named csmGetDrawableRenderOrders before 5-r.5.
22
+ inline const int* renderOrders(const Live2D::Cubism::Core::csmModel* model) {
23
+ return Live2D::Cubism::Core::csmGetRenderOrders(model);
24
+ }
25
+
26
+ } // namespace margelo::nitro::live2dskia::cubism
@@ -0,0 +1,27 @@
1
+ #pragma once
2
+ #include <stdexcept>
3
+ #include <string>
4
+ #include "CubismFramework.hpp"
5
+ #include "Id/CubismIdManager.hpp"
6
+ #include "Utils/CubismJson.hpp"
7
+
8
+ namespace margelo::nitro::live2dskia {
9
+
10
+ inline const Live2D::Cubism::Framework::csmByte* bytesOf(const std::string& s) {
11
+ return reinterpret_cast<const Live2D::Cubism::Framework::csmByte*>(s.data());
12
+ }
13
+
14
+ // The Framework's loaders don't check for parse errors and crash on invalid
15
+ // JSON, so parse it once up front and throw instead.
16
+ inline void requireValidJson(const std::string& json, const std::string& what) {
17
+ using namespace Live2D::Cubism::Framework;
18
+ Utils::CubismJson* parsed = Utils::CubismJson::Create(bytesOf(json), static_cast<csmSizeInt>(json.size()));
19
+ if (!parsed) throw std::runtime_error("Live2D: invalid " + what);
20
+ Utils::CubismJson::Delete(parsed);
21
+ }
22
+
23
+ inline Live2D::Cubism::Framework::CubismIdHandle idOf(const Live2D::Cubism::Framework::csmChar* name) {
24
+ return Live2D::Cubism::Framework::CubismFramework::GetIdManager()->GetId(name);
25
+ }
26
+
27
+ } // namespace margelo::nitro::live2dskia
@@ -0,0 +1,14 @@
1
+ // The Cubism Framework references a few renderer entry points that each
2
+ // Rendering/<backend> normally defines. We draw with Skia instead, so no
3
+ // backend is compiled and these are stubbed out.
4
+ #include "Rendering/CubismRenderer.hpp"
5
+
6
+ namespace Live2D::Cubism::Framework::Rendering {
7
+
8
+ CubismRenderer* CubismRenderer::Create(csmUint32, csmUint32) {
9
+ return nullptr;
10
+ }
11
+
12
+ void CubismRenderer::StaticRelease() {}
13
+
14
+ } // namespace Live2D::Cubism::Framework::Rendering
@@ -0,0 +1,54 @@
1
+ #include "CubismSetup.hpp"
2
+ #include <cstdio>
3
+ #include <cstdlib>
4
+ #include <mutex>
5
+ #include "CubismFramework.hpp"
6
+ #include "ICubismAllocator.hpp"
7
+
8
+ #ifdef __ANDROID__
9
+ #include <android/log.h>
10
+ #endif
11
+
12
+ namespace margelo::nitro::live2dskia {
13
+
14
+ namespace {
15
+
16
+ using namespace Live2D::Cubism::Framework;
17
+
18
+ class Allocator : public ICubismAllocator {
19
+ public:
20
+ void* Allocate(const csmSizeType size) override { return std::malloc(size); }
21
+ void Deallocate(void* memory) override { std::free(memory); }
22
+ void* AllocateAligned(const csmSizeType size, const csmUint32 alignment) override {
23
+ void* p = nullptr;
24
+ // posix_memalign needs at least pointer alignment.
25
+ const size_t a = alignment < sizeof(void*) ? sizeof(void*) : alignment;
26
+ return posix_memalign(&p, a, size) == 0 ? p : nullptr;
27
+ }
28
+ void DeallocateAligned(void* alignedMemory) override { std::free(alignedMemory); }
29
+ };
30
+
31
+ void log(const char* message) {
32
+ #ifdef __ANDROID__
33
+ __android_log_print(ANDROID_LOG_INFO, "Live2dSkia", "%s", message);
34
+ #else
35
+ std::printf("[Live2dSkia] %s", message);
36
+ #endif
37
+ }
38
+
39
+ } // namespace
40
+
41
+ void ensureCubismFramework() {
42
+ static std::once_flag once;
43
+ std::call_once(once, [] {
44
+ // Both live for the whole process: the Framework keeps pointers to them.
45
+ static Allocator allocator;
46
+ static CubismFramework::Option option{};
47
+ option.LogFunction = log;
48
+ option.LoggingLevel = CubismFramework::Option::LogLevel_Info;
49
+ CubismFramework::StartUp(&allocator, &option);
50
+ CubismFramework::Initialize();
51
+ });
52
+ }
53
+
54
+ } // namespace margelo::nitro::live2dskia
@@ -0,0 +1,9 @@
1
+ #pragma once
2
+
3
+ namespace margelo::nitro::live2dskia {
4
+
5
+ // Starts up and initializes the Cubism Framework once per process.
6
+ // Safe to call from any thread, any number of times.
7
+ void ensureCubismFramework();
8
+
9
+ } // namespace margelo::nitro::live2dskia
@@ -0,0 +1,25 @@
1
+ #pragma once
2
+ #include <map>
3
+ #include "CubismModelSettingJson.hpp"
4
+ #include "Motion/ICubismUpdater.hpp"
5
+
6
+ namespace margelo::nitro::live2dskia {
7
+
8
+ // Applies setParameter values every update, after look and breath (so they
9
+ // can be overridden) and before physics (so hair and clothes follow).
10
+ class HeldParameterUpdater : public Live2D::Cubism::Framework::ICubismUpdater {
11
+ public:
12
+ explicit HeldParameterUpdater(const std::map<Live2D::Cubism::Framework::CubismIdHandle, float>& values)
13
+ : ICubismUpdater(Live2D::Cubism::Framework::CubismUpdateOrder_Breath + 50), _values(values) {}
14
+
15
+ void OnLateUpdate(Live2D::Cubism::Framework::CubismModel* model, Live2D::Cubism::Framework::csmFloat32) override {
16
+ for (const auto& [id, value] : _values) {
17
+ model->SetParameterValue(id, value);
18
+ }
19
+ }
20
+
21
+ private:
22
+ const std::map<Live2D::Cubism::Framework::CubismIdHandle, float>& _values;
23
+ };
24
+
25
+ } // namespace margelo::nitro::live2dskia