mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93ca083d48 | ||
|
|
fa9478edf6 | ||
|
|
36e50678d6 | ||
|
|
3acd0c4f2c | ||
|
|
6dc73f5d79 |
@@ -1892,6 +1892,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers()
|
||||
if (fenceStatus == VK_SUCCESS)
|
||||
{
|
||||
ProcessDestructionQueue();
|
||||
m_uniformVarBufferReadIndex = m_cmdBufferUniformRingbufIndices[m_commandBufferSyncIndex];
|
||||
m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size();
|
||||
memoryManager->cleanupBuffers(m_countCommandBufferFinished);
|
||||
m_countCommandBufferFinished++;
|
||||
@@ -1985,6 +1986,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
|
||||
cemuLog_logDebug(LogType::Force, "Vulkan: Waiting for available command buffer...");
|
||||
WaitForNextFinishedCommandBuffer();
|
||||
}
|
||||
m_cmdBufferUniformRingbufIndices[nextCmdBufferIndex] = m_cmdBufferUniformRingbufIndices[m_commandBufferIndex];
|
||||
m_commandBufferIndex = nextCmdBufferIndex;
|
||||
|
||||
|
||||
@@ -3562,13 +3564,13 @@ void VulkanRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType,
|
||||
switch (shaderType)
|
||||
{
|
||||
case LatteConst::ShaderType::Vertex:
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = offset;
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = offset;
|
||||
break;
|
||||
case LatteConst::ShaderType::Geometry:
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = offset;
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = offset;
|
||||
break;
|
||||
case LatteConst::ShaderType::Pixel:
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = offset;
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = offset;
|
||||
break;
|
||||
default:
|
||||
cemu_assert_debug(false);
|
||||
|
||||
@@ -591,6 +591,7 @@ private:
|
||||
bool m_uniformVarBufferMemoryIsCoherent{false};
|
||||
uint8* m_uniformVarBufferPtr = nullptr;
|
||||
uint32 m_uniformVarBufferWriteIndex = 0;
|
||||
uint32 m_uniformVarBufferReadIndex = 0;
|
||||
|
||||
// transform feedback ringbuffer
|
||||
VkBuffer m_xfbRingBuffer = VK_NULL_HANDLE;
|
||||
@@ -637,6 +638,7 @@ private:
|
||||
size_t m_commandBufferIndex = 0; // current buffer being filled
|
||||
size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit)
|
||||
size_t m_commandBufferIDOfPrevFrame = 0;
|
||||
std::array<size_t, kCommandBufferPoolSize> m_cmdBufferUniformRingbufIndices {}; // index in the uniform ringbuffer
|
||||
std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences;
|
||||
std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers;
|
||||
std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores;
|
||||
@@ -659,7 +661,7 @@ private:
|
||||
uint32 uniformVarBufferOffset[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT];
|
||||
struct
|
||||
{
|
||||
uint32 unformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS];
|
||||
uint32 uniformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS];
|
||||
}shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT];
|
||||
}dynamicOffsetInfo{};
|
||||
|
||||
|
||||
@@ -375,24 +375,20 @@ float s_vkUniformData[512 * 4];
|
||||
|
||||
void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader)
|
||||
{
|
||||
auto GET_UNIFORM_DATA_PTR = [&](size_t index) { return s_vkUniformData + (index / 4); };
|
||||
auto GET_UNIFORM_DATA_PTR = [](size_t index) { return s_vkUniformData + (index / 4); };
|
||||
|
||||
sint32 shaderAluConst;
|
||||
sint32 shaderUniformRegisterOffset;
|
||||
|
||||
switch (shader->shaderType)
|
||||
{
|
||||
case LatteConst::ShaderType::Vertex:
|
||||
shaderAluConst = 0x400;
|
||||
shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START;
|
||||
break;
|
||||
case LatteConst::ShaderType::Pixel:
|
||||
shaderAluConst = 0;
|
||||
shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START;
|
||||
break;
|
||||
case LatteConst::ShaderType::Geometry:
|
||||
shaderAluConst = 0; // geometry shader has no ALU const
|
||||
shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
@@ -445,7 +441,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt
|
||||
}
|
||||
if (shader->uniform.loc_verticesPerInstance >= 0)
|
||||
{
|
||||
*(int*)(s_vkUniformData + ((size_t)shader->uniform.loc_verticesPerInstance / 4)) = m_streamoutState.verticesPerInstance;
|
||||
*(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance;
|
||||
for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++)
|
||||
{
|
||||
if (shader->uniform.loc_streamoutBufferBase[b] >= 0)
|
||||
@@ -455,26 +451,63 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt
|
||||
}
|
||||
}
|
||||
// upload
|
||||
if ((m_uniformVarBufferWriteIndex + shader->uniform.uniformRangeSize + 1024) > UNIFORMVAR_RINGBUFFER_SIZE)
|
||||
const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1;
|
||||
const uint32 uniformSize = (shader->uniform.uniformRangeSize + bufferAlignmentM1) & ~bufferAlignmentM1;
|
||||
|
||||
auto waitWhileCondition = [&](std::function<bool()> condition) {
|
||||
while (condition())
|
||||
{
|
||||
if (m_commandBufferSyncIndex == m_commandBufferIndex)
|
||||
{
|
||||
if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex)
|
||||
{
|
||||
draw_endRenderPass();
|
||||
SubmitCommandBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
// submitting work would not change readIndex, so there's no way for conditions based on it to change
|
||||
cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption");
|
||||
cemu_assert_suspicious();
|
||||
break;
|
||||
}
|
||||
}
|
||||
WaitForNextFinishedCommandBuffer();
|
||||
}
|
||||
};
|
||||
|
||||
// wrap around if it doesnt fit consecutively
|
||||
if (m_uniformVarBufferWriteIndex + uniformSize > UNIFORMVAR_RINGBUFFER_SIZE)
|
||||
{
|
||||
waitWhileCondition([&]() {
|
||||
return m_uniformVarBufferReadIndex > m_uniformVarBufferWriteIndex || m_uniformVarBufferReadIndex == 0;
|
||||
});
|
||||
m_uniformVarBufferWriteIndex = 0;
|
||||
}
|
||||
uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1;
|
||||
|
||||
auto ringBufRemaining = [&]() {
|
||||
ssize_t ringBufferUsedBytes = (ssize_t)m_uniformVarBufferWriteIndex - m_uniformVarBufferReadIndex;
|
||||
if (ringBufferUsedBytes < 0)
|
||||
ringBufferUsedBytes += UNIFORMVAR_RINGBUFFER_SIZE;
|
||||
return UNIFORMVAR_RINGBUFFER_SIZE - 1 - ringBufferUsedBytes;
|
||||
};
|
||||
waitWhileCondition([&]() {
|
||||
return ringBufRemaining() < uniformSize;
|
||||
});
|
||||
|
||||
const uint32 uniformOffset = m_uniformVarBufferWriteIndex;
|
||||
memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize);
|
||||
m_uniformVarBufferWriteIndex += shader->uniform.uniformRangeSize;
|
||||
m_uniformVarBufferWriteIndex = (m_uniformVarBufferWriteIndex + bufferAlignmentM1) & ~bufferAlignmentM1;
|
||||
m_uniformVarBufferWriteIndex += uniformSize;
|
||||
// update dynamic offset
|
||||
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset;
|
||||
// flush if not coherent
|
||||
if (!m_uniformVarBufferMemoryIsCoherent)
|
||||
{
|
||||
uint32 nonCoherentAtomSizeM1 = m_featureControl.limits.nonCoherentAtomSize - 1;
|
||||
VkMappedMemoryRange flushedRange{};
|
||||
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||
flushedRange.memory = m_uniformVarBufferMemory;
|
||||
flushedRange.offset = uniformOffset;
|
||||
flushedRange.size = (shader->uniform.uniformRangeSize + nonCoherentAtomSizeM1) & ~nonCoherentAtomSizeM1;
|
||||
flushedRange.size = uniformSize;
|
||||
vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange);
|
||||
}
|
||||
}
|
||||
@@ -494,7 +527,7 @@ void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderSta
|
||||
{
|
||||
for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex])
|
||||
{
|
||||
dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].unformBufferOffset[itr];
|
||||
dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].uniformBufferOffset[itr];
|
||||
numDynOffsets++;
|
||||
}
|
||||
}
|
||||
@@ -1357,6 +1390,24 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
return;
|
||||
}
|
||||
|
||||
// prepare streamout
|
||||
m_streamoutState.verticesPerInstance = count;
|
||||
LatteStreamout_PrepareDrawcall(count, instanceCount);
|
||||
|
||||
// update uniform vars
|
||||
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
|
||||
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
|
||||
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
|
||||
|
||||
if (vertexShader)
|
||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader);
|
||||
if (pixelShader)
|
||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader);
|
||||
if (geometryShader)
|
||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader);
|
||||
// store where the read pointer should go after command buffer execution
|
||||
m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex;
|
||||
|
||||
// process index data
|
||||
const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]);
|
||||
|
||||
@@ -1410,22 +1461,6 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount);
|
||||
}
|
||||
|
||||
// prepare streamout
|
||||
m_streamoutState.verticesPerInstance = count;
|
||||
LatteStreamout_PrepareDrawcall(count, instanceCount);
|
||||
|
||||
// update uniform vars
|
||||
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
|
||||
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
|
||||
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
|
||||
|
||||
if (vertexShader)
|
||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader);
|
||||
if (pixelShader)
|
||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader);
|
||||
if (geometryShader)
|
||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader);
|
||||
|
||||
PipelineInfo* pipeline_info;
|
||||
|
||||
if (!isFirst)
|
||||
@@ -1613,13 +1648,13 @@ void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader
|
||||
switch (shaderType)
|
||||
{
|
||||
case LatteConst::ShaderType::Vertex:
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||
break;
|
||||
case LatteConst::ShaderType::Geometry:
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||
break;
|
||||
case LatteConst::ShaderType::Pixel:
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
|
||||
@@ -91,7 +91,11 @@ bool cemuLog_log(LogType type, std::basic_string<T> formatStr, TArgs&&... args)
|
||||
else
|
||||
{
|
||||
const auto format_view = fmt::basic_string_view<T>(formatStr);
|
||||
#if FMT_VERSION >= 110000
|
||||
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffered_context<T>>(args...));
|
||||
#else
|
||||
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffer_context<T>>(args...));
|
||||
#endif
|
||||
cemuLog_log(type, std::basic_string_view(text.data(), text.size()));
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
@@ -0,0 +1,4 @@
|
||||
/build
|
||||
/src/main/assets/hash.txt
|
||||
*.po
|
||||
*.pot
|
||||
@@ -0,0 +1,239 @@
|
||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||
import java.io.IOException
|
||||
import java.security.MessageDigest
|
||||
import java.util.regex.Pattern
|
||||
import javax.xml.bind.DatatypeConverter
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.kotlinx.gettext)
|
||||
alias(libs.plugins.aboutlibraries.android)
|
||||
}
|
||||
|
||||
fun String.runCommand(workingDir: File = File(".")): String? {
|
||||
try {
|
||||
val proc = ProcessBuilder(*trim().split("\\s".toRegex()).toTypedArray())
|
||||
.directory(workingDir)
|
||||
.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
||||
.redirectError(ProcessBuilder.Redirect.PIPE)
|
||||
.start()
|
||||
assert(proc.waitFor(1, TimeUnit.MINUTES))
|
||||
return proc.inputStream.bufferedReader().readText()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun getGitHash(): String? = "git log --format=%h -1".runCommand()?.trim()
|
||||
|
||||
val versionMajor: Int? = System.getenv("EMULATOR_VERSION_MAJOR")?.toIntOrNull()
|
||||
val versionMinor: Int? = System.getenv("EMULATOR_VERSION_MINOR")?.toIntOrNull()
|
||||
versionMajor
|
||||
fun getVersionName(): String {
|
||||
if (versionMajor != null && versionMinor != null)
|
||||
return "$versionMajor.$versionMinor"
|
||||
return getGitHash() ?: "1.0"
|
||||
}
|
||||
|
||||
fun getVersionCode(): Int = System.getenv("VERSION_CODE")?.toIntOrNull() ?: 1
|
||||
|
||||
val cemuDataFilesFolder = "../../../bin"
|
||||
|
||||
android {
|
||||
namespace = "com.izzy2lost.weeu"
|
||||
compileSdk = 36
|
||||
ndkVersion = "28.2.13676358"
|
||||
defaultConfig {
|
||||
applicationId = "com.izzy2lost.weeu"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = getVersionCode()
|
||||
versionName = getVersionName()
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
androidResources {
|
||||
ignoreAssetsPattern = "!*cemu.mo:"
|
||||
}
|
||||
|
||||
sourceSets.getByName("main") {
|
||||
assets {
|
||||
srcDir(cemuDataFilesFolder)
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs.useLegacyPackaging = true
|
||||
}
|
||||
|
||||
val keystoreFilePath: String? = System.getenv("ANDROID_STORE_FILE")
|
||||
|
||||
signingConfigs {
|
||||
if (keystoreFilePath != null) {
|
||||
create("release") {
|
||||
storeFile = file(keystoreFilePath)
|
||||
storePassword = System.getenv("ANDROID_KEY_STORE_PASSWORD")
|
||||
keyAlias = System.getenv("ANDROID_KEY_ALIAS")
|
||||
keyPassword = System.getenv("ANDROID_KEY_STORE_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = if (keystoreFilePath != null) {
|
||||
signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility(JavaVersion.VERSION_17)
|
||||
targetCompatibility(JavaVersion.VERSION_17)
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.25.0+"
|
||||
path = file("../../../CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments(
|
||||
"-DANDROID_STL=c++_shared",
|
||||
"-DENABLE_VCPKG=ON",
|
||||
"-DVCPKG_TARGET_ANDROID=ON",
|
||||
"-DENABLE_SDL=OFF",
|
||||
"-DENABLE_WXWIDGETS=OFF",
|
||||
"-DENABLE_OPENGL=OFF",
|
||||
"-DENABLE_BLUEZ=OFF",
|
||||
"-DBUNDLE_SPEEX=ON",
|
||||
"-DENABLE_DISCORD_RPC=OFF",
|
||||
"-DENABLE_NSYSHID_LIBUSB=OFF",
|
||||
"-DENABLE_WAYLAND=OFF",
|
||||
"-DENABLE_HIDAPI=OFF"
|
||||
)
|
||||
if (versionMajor != null && versionMinor != null) {
|
||||
arguments.addAll(
|
||||
arrayOf(
|
||||
"-DEMULATOR_VERSION_MAJOR=$versionMajor",
|
||||
"-DEMULATOR_VERSION_MINOR=$versionMinor"
|
||||
)
|
||||
)
|
||||
}
|
||||
abiFilters("arm64-v8a")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
compose = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ComputeCemuDataFilesHashTask : DefaultTask() {
|
||||
private val ignoreFilePatterns = arrayOf(
|
||||
Pattern.compile(".*cemu\\.mo"),
|
||||
Pattern.compile(".*Cemu_(?:debug|release)"),
|
||||
)
|
||||
|
||||
@get:Input
|
||||
abstract val cemuDataFolder: Property<String>
|
||||
|
||||
private fun isFileIgnored(file: File): Boolean {
|
||||
return ignoreFilePatterns.any { pattern -> pattern.matcher(file.path).matches() }
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun computeCemuDataFilesHash() {
|
||||
val assetDir = File(project.projectDir, "src/main/assets")
|
||||
if (!assetDir.exists()) {
|
||||
assetDir.mkdirs()
|
||||
}
|
||||
|
||||
val cemuDataFilesDir = File(project.projectDir, cemuDataFolder.get())
|
||||
val hashFile = File(assetDir, "hash.txt")
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
if (!cemuDataFilesDir.isDirectory) {
|
||||
hashFile.writeText("invalid")
|
||||
return
|
||||
}
|
||||
|
||||
val fileHashes = cemuDataFilesDir.walkTopDown()
|
||||
.filter { it.isFile && !isFileIgnored(it) }
|
||||
.sortedBy { it.path }
|
||||
.map {
|
||||
md.reset()
|
||||
md.update(it.path.toByteArray())
|
||||
md.update(it.readBytes())
|
||||
md.digest()
|
||||
}
|
||||
.toList()
|
||||
|
||||
md.reset()
|
||||
fileHashes.forEach { md.update(it) }
|
||||
|
||||
hashFile.writeText(DatatypeConverter.printHexBinary(md.digest()))
|
||||
}
|
||||
}
|
||||
|
||||
val computeCemuDataFilesHashTask =
|
||||
tasks.register<ComputeCemuDataFilesHashTask>("computeCemuDataFilesHash") {
|
||||
cemuDataFolder = cemuDataFilesFolder
|
||||
}
|
||||
tasks.preBuild.dependsOn(computeCemuDataFilesHashTask)
|
||||
|
||||
gettext {
|
||||
potFile.set(File(projectDir, "cemu_kt.pot"))
|
||||
keywords.set(listOf("tr", "trNoop"))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.aboutlibraries.compose.m3)
|
||||
implementation(libs.kotlinx.gettext)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.ui)
|
||||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.archunit.junit4)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.okhttp.coroutines)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.google.android.material)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
-dontobfuscate
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.izzy2lost.weeu.tests
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PlaceholderInstrumentedTest {
|
||||
@Test
|
||||
fun test() {
|
||||
assertTrue(true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:insetLeft="25%"
|
||||
android:insetTop="25%"
|
||||
android:insetRight="25%"
|
||||
android:insetBottom="25%">
|
||||
<vector
|
||||
android:width="14dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m480,840q-65,0 -120.5,-32Q304,776 272,720L160,720v-80h84q-3,-20 -3.5,-40 -0.5,-20 -0.5,-40h-80v-80h80q0,-20 0.5,-40 0.5,-20 3.5,-40h-84v-80h112q14,-23 31.5,-43 17.5,-20 40.5,-35l-64,-66 56,-56 86,86q28,-9 57,-9 29,0 57,9l88,-86 56,56 -66,66q23,15 41.5,34.5 18.5,19.5 32.5,43.5h112v80h-84q3,20 3.5,40 0.5,20 0.5,40h80v80h-80q0,20 -0.5,40 -0.5,20 -3.5,40h84v80L688,720q-32,56 -87.5,88 -55.5,32 -120.5,32zM400,640h160v-80L400,560ZM400,480h160v-80L400,400Z" />
|
||||
</vector>
|
||||
</inset>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_debug" />
|
||||
<monochrome android:drawable="@drawable/ic_debug" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_debug" />
|
||||
<monochrome android:drawable="@drawable/ic_debug" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Wee U debug</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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.VIBRATE" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.vulkan.version"
|
||||
android:required="true"
|
||||
android:version="0x401000" />
|
||||
|
||||
<application
|
||||
android:name=".CemuApplication"
|
||||
android:allowBackup="true"
|
||||
android:appCategory="game"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.WeeU">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".emulation.EmulationActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:screenOrientation="userLandscape"
|
||||
tools:ignore="DiscouragedApi">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*" />
|
||||
<data android:host="*" />
|
||||
<data android:pathPattern=".*\\.wud" />
|
||||
<data android:pathPattern=".*\\.wux" />
|
||||
<data android:pathPattern=".*\\.wua" />
|
||||
<data android:pathPattern=".*\\.wuhb" />
|
||||
<data android:pathPattern=".*\\.iso" />
|
||||
<data android:pathPattern=".*\\.elf" />
|
||||
<data android:pathPattern=".*\\.rpx" />
|
||||
<!--
|
||||
TODO?
|
||||
<data android:pathPattern=".*/title.tmd" />
|
||||
-->
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name=".provider.DocumentsProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "AndroidAudio.h"
|
||||
|
||||
#include "Cafe/OS/libs/snd_core/ax.h"
|
||||
#include "audio/IAudioAPI.h"
|
||||
|
||||
#if HAS_CUBEB
|
||||
#include "audio/CubebAPI.h"
|
||||
#endif // HAS_CUBEB
|
||||
|
||||
namespace AndroidAudio
|
||||
{
|
||||
|
||||
void createAudioDevice(IAudioAPI::AudioAPI audioApi, sint32 channels, sint32 volume, bool isTV)
|
||||
{
|
||||
static constexpr int AX_FRAMES_PER_GROUP = 4;
|
||||
std::unique_lock lock(g_audioMutex);
|
||||
auto& audioDevice = isTV ? g_tvAudio : g_padAudio;
|
||||
switch (channels)
|
||||
{
|
||||
case 0:
|
||||
channels = 1;
|
||||
break;
|
||||
case 2:
|
||||
channels = 6;
|
||||
break;
|
||||
default: // stereo
|
||||
channels = 2;
|
||||
break;
|
||||
}
|
||||
switch (audioApi)
|
||||
{
|
||||
#if HAS_CUBEB
|
||||
case IAudioAPI::AudioAPI::Cubeb:
|
||||
{
|
||||
audioDevice.reset();
|
||||
std::shared_ptr<CubebAPI::CubebDeviceDescription> deviceDescriptionPtr = std::make_shared<CubebAPI::CubebDeviceDescription>(nullptr, std::string(), std::wstring());
|
||||
audioDevice = IAudioAPI::CreateDevice(IAudioAPI::AudioAPI::Cubeb, deviceDescriptionPtr, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||
audioDevice->SetVolume(volume);
|
||||
break;
|
||||
}
|
||||
#endif // HAS_CUBEB
|
||||
default:
|
||||
cemuLog_log(LogType::Force, "Invalid audio api: {}", audioApi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setAudioVolume(sint32 volume, bool isTV)
|
||||
{
|
||||
std::shared_lock lock(g_audioMutex);
|
||||
auto& audioDevice = isTV ? g_tvAudio : g_padAudio;
|
||||
if (audioDevice)
|
||||
audioDevice->SetVolume(volume);
|
||||
}
|
||||
|
||||
}; // namespace AndroidAudio
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "audio/IAudioAPI.h"
|
||||
|
||||
namespace AndroidAudio
|
||||
{
|
||||
void createAudioDevice(IAudioAPI::AudioAPI audioApi, sint32 channels, sint32 volume, bool isTV = true);
|
||||
void setAudioVolume(sint32 volume, bool isTV = true);
|
||||
}; // namespace AndroidAudio
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "AndroidEmulatedController.h"
|
||||
|
||||
std::array<std::unique_ptr<AndroidEmulatedController>, InputManager::kMaxController> AndroidEmulatedController::s_emulatedControllers;
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include "input/InputManager.h"
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/emulated/ClassicController.h"
|
||||
#include "input/emulated/ProController.h"
|
||||
#include "input/emulated/WiimoteController.h"
|
||||
|
||||
class AndroidEmulatedController {
|
||||
private:
|
||||
size_t m_index;
|
||||
static std::array<std::unique_ptr<AndroidEmulatedController>, InputManager::kMaxController> s_emulatedControllers;
|
||||
EmulatedControllerPtr m_emulatedController;
|
||||
AndroidEmulatedController(size_t index)
|
||||
: m_index(index)
|
||||
{
|
||||
m_emulatedController = InputManager::instance().get_controller(m_index);
|
||||
}
|
||||
|
||||
public:
|
||||
static AndroidEmulatedController& getAndroidEmulatedController(size_t index)
|
||||
{
|
||||
auto& controller = s_emulatedControllers.at(index);
|
||||
if (!controller)
|
||||
controller = std::unique_ptr<AndroidEmulatedController>(new AndroidEmulatedController(index));
|
||||
return *controller;
|
||||
}
|
||||
void setButtonValue(uint64 mappingId, bool value)
|
||||
{
|
||||
if (!m_emulatedController)
|
||||
return;
|
||||
m_emulatedController->setButtonValue(mappingId, value);
|
||||
}
|
||||
void setAxisValue(uint64 mappingId, float value)
|
||||
{
|
||||
if (!m_emulatedController)
|
||||
return;
|
||||
m_emulatedController->setAxisValue(mappingId, value);
|
||||
}
|
||||
void setType(EmulatedController::Type type)
|
||||
{
|
||||
if (m_emulatedController && m_emulatedController->type() == type)
|
||||
return;
|
||||
m_emulatedController = InputManager::instance().set_controller(m_index, type);
|
||||
InputManager::instance().save(m_index);
|
||||
}
|
||||
void setMapping(uint64 mappingId, ControllerPtr controller, uint64 buttonId)
|
||||
{
|
||||
if (m_emulatedController && controller)
|
||||
{
|
||||
const auto& controllers = m_emulatedController->get_controllers();
|
||||
auto controllerIt = std::find_if(controllers.begin(), controllers.end(), [&](const ControllerPtr& c) { return c->api() == controller->api() && c->uuid() == controller->uuid(); });
|
||||
if (controllerIt == controllers.end())
|
||||
m_emulatedController->add_controller(controller);
|
||||
else
|
||||
controller = *controllerIt;
|
||||
m_emulatedController->set_mapping(mappingId, controller, buttonId);
|
||||
InputManager::instance().save(m_index);
|
||||
}
|
||||
}
|
||||
std::optional<std::string> getMapping(uint64 mapping) const
|
||||
{
|
||||
if (!m_emulatedController)
|
||||
return {};
|
||||
auto controller = m_emulatedController->get_mapping_controller(mapping);
|
||||
if (!controller)
|
||||
return {};
|
||||
auto mappingName = m_emulatedController->get_mapping_name(mapping);
|
||||
return fmt::format("{}: {}", controller->display_name(), mappingName);
|
||||
}
|
||||
std::map<uint64, std::string> getMappings() const
|
||||
{
|
||||
if (!m_emulatedController)
|
||||
return {};
|
||||
std::map<uint64, std::string> mappings;
|
||||
auto type = m_emulatedController->type();
|
||||
uint64 mapping = 0;
|
||||
uint64 maxMapping = 0;
|
||||
if (type == EmulatedController::Type::VPAD)
|
||||
{
|
||||
mapping = VPADController::ButtonId::kButtonId_A;
|
||||
maxMapping = VPADController::ButtonId::kButtonId_Max;
|
||||
}
|
||||
if (type == EmulatedController::Type::Pro)
|
||||
{
|
||||
mapping = ProController::ButtonId::kButtonId_A;
|
||||
maxMapping = ProController::ButtonId::kButtonId_Max;
|
||||
}
|
||||
if (type == EmulatedController::Type::Classic)
|
||||
{
|
||||
mapping = ClassicController::ButtonId::kButtonId_A;
|
||||
maxMapping = ClassicController::ButtonId::kButtonId_Max;
|
||||
}
|
||||
if (type == EmulatedController::Type::Wiimote)
|
||||
{
|
||||
mapping = WiimoteController::ButtonId::kButtonId_A;
|
||||
maxMapping = WiimoteController::ButtonId::kButtonId_Max;
|
||||
}
|
||||
for (; mapping < maxMapping; mapping++)
|
||||
{
|
||||
auto mappingName = getMapping(mapping);
|
||||
if (mappingName.has_value())
|
||||
mappings[mapping] = mappingName.value();
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
void setDisabled()
|
||||
{
|
||||
InputManager::instance().delete_controller(m_index, true);
|
||||
m_emulatedController.reset();
|
||||
}
|
||||
EmulatedControllerPtr getEmulatedController()
|
||||
{
|
||||
return m_emulatedController;
|
||||
}
|
||||
|
||||
void clearMapping(uint64 mapping)
|
||||
{
|
||||
if (!m_emulatedController)
|
||||
return;
|
||||
m_emulatedController->delete_mapping(mapping);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "JNIUtils.h"
|
||||
#include "Common/android/FilesystemAndroid.h"
|
||||
|
||||
class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks {
|
||||
jmethodID m_openContentUriMid;
|
||||
jmethodID m_listFilesMid;
|
||||
jmethodID m_isDirectoryMid;
|
||||
jmethodID m_isFileMid;
|
||||
jmethodID m_existsMid;
|
||||
JNIUtils::Scopedjclass m_fileUtilClass;
|
||||
|
||||
bool CallBooleanFunction(const std::filesystem::path& uri, jmethodID methodId)
|
||||
{
|
||||
bool result = false;
|
||||
JNIUtils::fiberSafeJNICall([&](JNIEnv* env) {
|
||||
jstring uriString = JNIUtils::toJString(env, uri);
|
||||
result = env->CallStaticBooleanMethod(*m_fileUtilClass, methodId, uriString);
|
||||
env->DeleteLocalRef(uriString);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
AndroidFilesystemCallbacks()
|
||||
{
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
m_fileUtilClass = JNIUtils::Scopedjclass("com/izzy2lost/weeu/nativeinterface/NativeFiles");
|
||||
m_openContentUriMid = env->GetStaticMethodID(*m_fileUtilClass, "openContentUri", "(Ljava/lang/String;)I");
|
||||
m_listFilesMid = env->GetStaticMethodID(*m_fileUtilClass, "listFiles", "(Ljava/lang/String;)[Ljava/lang/String;");
|
||||
m_isDirectoryMid = env->GetStaticMethodID(*m_fileUtilClass, "isDirectory", "(Ljava/lang/String;)Z");
|
||||
m_isFileMid = env->GetStaticMethodID(*m_fileUtilClass, "isFile", "(Ljava/lang/String;)Z");
|
||||
m_existsMid = env->GetStaticMethodID(*m_fileUtilClass, "exists", "(Ljava/lang/String;)Z");
|
||||
}
|
||||
|
||||
int OpenContentUri(const std::filesystem::path& uri) override
|
||||
{
|
||||
int fd = -1;
|
||||
JNIUtils::fiberSafeJNICall([&](JNIEnv* env) {
|
||||
jstring uriString = JNIUtils::toJString(env, uri);
|
||||
fd = env->CallStaticIntMethod(*m_fileUtilClass, m_openContentUriMid, uriString);
|
||||
env->DeleteLocalRef(uriString);
|
||||
});
|
||||
return fd;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> ListFiles(const std::filesystem::path& uri) override
|
||||
{
|
||||
std::vector<std::filesystem::path> paths;
|
||||
JNIUtils::fiberSafeJNICall([&](JNIEnv* env) {
|
||||
jstring uriString = JNIUtils::toJString(env, uri);
|
||||
jobjectArray pathsObjArray = static_cast<jobjectArray>(env->CallStaticObjectMethod(*m_fileUtilClass, m_listFilesMid, uriString));
|
||||
env->DeleteLocalRef(uriString);
|
||||
jsize arrayLength = env->GetArrayLength(pathsObjArray);
|
||||
paths.reserve(arrayLength);
|
||||
for (jsize i = 0; i < arrayLength; i++)
|
||||
{
|
||||
jstring pathStr = static_cast<jstring>(env->GetObjectArrayElement(pathsObjArray, i));
|
||||
paths.push_back(JNIUtils::toString(env, pathStr));
|
||||
env->DeleteLocalRef(pathStr);
|
||||
}
|
||||
env->DeleteLocalRef(pathsObjArray);
|
||||
});
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool IsDirectory(const std::filesystem::path& uri) override
|
||||
{
|
||||
return CallBooleanFunction(uri, m_isDirectoryMid);
|
||||
}
|
||||
|
||||
bool IsFile(const std::filesystem::path& uri) override
|
||||
{
|
||||
return CallBooleanFunction(uri, m_isFileMid);
|
||||
}
|
||||
|
||||
bool Exists(const std::filesystem::path& uri) override
|
||||
{
|
||||
return CallBooleanFunction(uri, m_existsMid);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "GameTitleLoader.h"
|
||||
#include "JNIUtils.h"
|
||||
#include <android/bitmap.h>
|
||||
// TODO: Refactor this:
|
||||
class AndroidGameTitleLoadedCallback : public GameTitleLoadedCallback
|
||||
{
|
||||
jmethodID m_onGameTitleLoadedMID;
|
||||
JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj;
|
||||
jmethodID m_gameConstructorMID;
|
||||
JNIUtils::Scopedjclass m_gamejclass{"com/izzy2lost/weeu/nativeinterface/NativeGameTitles$Game"};
|
||||
jmethodID m_createBitmapMID;
|
||||
JNIUtils::Scopedjclass m_bitmapClass{"android/graphics/Bitmap"};
|
||||
JNIUtils::Scopedjobject m_bitmapFormat;
|
||||
|
||||
public:
|
||||
AndroidGameTitleLoadedCallback(jmethodID onGameTitleLoadedMID, jobject gameTitleLoadedCallbackObj)
|
||||
: m_onGameTitleLoadedMID(onGameTitleLoadedMID),
|
||||
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj)
|
||||
{
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
m_bitmapFormat = JNIUtils::getEnumValue(env, "android/graphics/Bitmap$Config", "ARGB_8888");
|
||||
m_gameConstructorMID = env->GetMethodID(*m_gamejclass, "<init>", "(JLjava/lang/String;Ljava/lang/String;SSISSSIZLandroid/graphics/Bitmap;)V");
|
||||
m_createBitmapMID = env->GetStaticMethodID(*m_bitmapClass, "createBitmap", "([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
}
|
||||
|
||||
void onTitleLoaded(const Game& game, const std::shared_ptr<Image>& icon) override
|
||||
{
|
||||
static JNIUtils::ScopedJNIENV env;
|
||||
jstring name = JNIUtils::toJString(env, game.name);
|
||||
jstring path = game.path.has_value() ? JNIUtils::toJString(env, game.path.value()) : nullptr;
|
||||
jobject bitmap = nullptr;
|
||||
sint32 lastPlayedYear = 0, lastPlayedMonth = 0, lastPlayedDay = 0;
|
||||
if (game.lastPlayed.has_value())
|
||||
{
|
||||
lastPlayedYear = static_cast<int>(game.lastPlayed->year());
|
||||
lastPlayedMonth = static_cast<unsigned int>(game.lastPlayed->month());
|
||||
lastPlayedDay = static_cast<unsigned int>(game.lastPlayed->day());
|
||||
}
|
||||
if (icon)
|
||||
{
|
||||
jintArray jIconData = env->NewIntArray(icon->m_width * icon->m_height);
|
||||
env->SetIntArrayRegion(jIconData, 0, icon->m_width * icon->m_height, icon->m_colors);
|
||||
bitmap = env->CallStaticObjectMethod(*m_bitmapClass, m_createBitmapMID, jIconData, icon->m_width, icon->m_height, *m_bitmapFormat);
|
||||
env->DeleteLocalRef(jIconData);
|
||||
}
|
||||
jobject gamejobject = env->NewObject(
|
||||
*m_gamejclass,
|
||||
m_gameConstructorMID,
|
||||
game.titleId,
|
||||
path,
|
||||
name,
|
||||
game.version,
|
||||
game.dlc,
|
||||
static_cast<sint32>(game.region),
|
||||
lastPlayedYear,
|
||||
lastPlayedMonth,
|
||||
lastPlayedDay,
|
||||
game.minutesPlayed,
|
||||
game.isFavorite,
|
||||
bitmap);
|
||||
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameTitleLoadedMID, gamejobject);
|
||||
env->DeleteLocalRef(gamejobject);
|
||||
if (bitmap != nullptr)
|
||||
env->DeleteLocalRef(bitmap);
|
||||
if (path != nullptr)
|
||||
env->DeleteLocalRef(path);
|
||||
env->DeleteLocalRef(name);
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user