mirror of
https://github.com/izzy2lost/Super3.git
synced 2026-07-05 15:18:38 -07:00
Virtual On 2: Oratorio Tangram now works, it takes awhile to boot. Better Supermodel.ini syncing.
This commit is contained in:
+16
-12
@@ -406,18 +406,22 @@ PowerPCFrequency = 40
|
||||
[ vf3tb ]
|
||||
PowerPCFrequency = 40
|
||||
;Virtual On 2: Oratorio Tangram (Revision B)
|
||||
[ von2 ]
|
||||
PowerPCFrequency = 30
|
||||
;Virtual On 2: Oratorio Tangram (Ver 5.4g)
|
||||
[ von254g ]
|
||||
PowerPCFrequency = 30
|
||||
;Virtual On 2: Oratorio Tangram (Revision A)
|
||||
[ von2a ]
|
||||
PowerPCFrequency = 30
|
||||
;Virtual On 2: Oratorio Tangram (?)
|
||||
[ von2o ]
|
||||
PowerPCFrequency = 35
|
||||
;Virtua Striker 2 (Step 2.0) - no special settings
|
||||
[ von2 ]
|
||||
PowerPCFrequency = 30
|
||||
AndroidSimpleShader = 1
|
||||
;Virtual On 2: Oratorio Tangram (Ver 5.4g)
|
||||
[ von254g ]
|
||||
PowerPCFrequency = 30
|
||||
AndroidSimpleShader = 1
|
||||
;Virtual On 2: Oratorio Tangram (Revision A)
|
||||
[ von2a ]
|
||||
PowerPCFrequency = 30
|
||||
AndroidSimpleShader = 1
|
||||
;Virtual On 2: Oratorio Tangram (?)
|
||||
[ von2o ]
|
||||
PowerPCFrequency = 35
|
||||
AndroidSimpleShader = 1
|
||||
;Virtua Striker 2 (Step 2.0) - no special settings
|
||||
[ vs2 ]
|
||||
;Virtua Striker 2 (Step 1.5) - no special settings
|
||||
[ vs215 ]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <unordered_map>
|
||||
#include "R3DFloat.h"
|
||||
#include "Util/BitCast.h"
|
||||
#include "OSD/Logger.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define MAX_RAM_VERTS 150000
|
||||
@@ -398,6 +399,16 @@ void CNew3D::RenderFrame(void)
|
||||
RenderViewport(0x800000);
|
||||
|
||||
m_vbo.Bind(true);
|
||||
#ifdef __ANDROID__
|
||||
const bool clampRamVerts = m_config["AndroidClampRamVerts"].ValueAsDefault<bool>(true);
|
||||
if (clampRamVerts && m_polyBufferRam.size() > MAX_RAM_VERTS) {
|
||||
if (!m_warnedRamOverflow) {
|
||||
ErrorLog("New3D: RAM vertex buffer overflow (size=%zu, max=%d). Clamping.", m_polyBufferRam.size(), MAX_RAM_VERTS);
|
||||
m_warnedRamOverflow = true;
|
||||
}
|
||||
m_polyBufferRam.resize(MAX_RAM_VERTS);
|
||||
}
|
||||
#endif
|
||||
m_vbo.BufferSubData(MAX_ROM_VERTS*sizeof(FVertex), m_polyBufferRam.size()*sizeof(FVertex), m_polyBufferRam.data());
|
||||
|
||||
if (!m_polyBufferRom.empty()) {
|
||||
@@ -493,6 +504,18 @@ void CNew3D::RenderFrame(void)
|
||||
DrawScrollFog(); // fog layer if applicable must be drawn here
|
||||
|
||||
m_vbo.Bind(true);
|
||||
#ifdef __ANDROID__
|
||||
{
|
||||
const bool clampRamVerts = m_config["AndroidClampRamVerts"].ValueAsDefault<bool>(true);
|
||||
if (clampRamVerts && m_polyBufferRam.size() > MAX_RAM_VERTS) {
|
||||
if (!m_warnedRamOverflow) {
|
||||
ErrorLog("New3D: RAM vertex buffer overflow (size=%zu, max=%d). Clamping.", m_polyBufferRam.size(), MAX_RAM_VERTS);
|
||||
m_warnedRamOverflow = true;
|
||||
}
|
||||
m_polyBufferRam.resize(MAX_RAM_VERTS);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
m_vbo.BufferSubData(MAX_ROM_VERTS*sizeof(FVertex), m_polyBufferRam.size()*sizeof(FVertex), m_polyBufferRam.data()); // upload all the dynamic data to GPU in one go
|
||||
|
||||
if (!m_polyBufferRom.empty()) {
|
||||
|
||||
@@ -244,6 +244,7 @@ private:
|
||||
bool m_sunClamp;
|
||||
bool m_shadeIsSigned;
|
||||
bool m_new3dAccurate;
|
||||
bool m_warnedRamOverflow = false;
|
||||
|
||||
// Stepping
|
||||
int m_step;
|
||||
|
||||
@@ -59,6 +59,13 @@ bool R3DShader::LoadShader(const char* vertexShader, const char* fragmentShader)
|
||||
const char* gShader = "";
|
||||
const char* fShader = fragmentShaderR3D;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (m_config["AndroidSimpleShader"].ValueAsDefault<bool>(false)) {
|
||||
vShader = vertexShaderR3DSimple;
|
||||
fShader = fragmentShaderR3DSimple;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID__
|
||||
if (quads) {
|
||||
vShader = vertexShaderR3DQuads;
|
||||
|
||||
@@ -2,7 +2,85 @@
|
||||
#define _R3DSHADERTRIANGLES_H_
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// GLES 3.0 shaders (full path adapted from desktop triangle shader).
|
||||
// GLES 3.0 shaders.
|
||||
// - Simple path: textured vertex-colour with optional alpha discard (older Android fallback).
|
||||
// - Full path: adapted from desktop triangle shader.
|
||||
static const char *vertexShaderR3DSimple = R"glsl(#version 300 es
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
// uniforms
|
||||
uniform float modelScale;
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 projMat;
|
||||
uniform int translatorMap;
|
||||
|
||||
// attributes
|
||||
layout(location=0) in vec4 inVertex;
|
||||
layout(location=1) in vec3 inNormal;
|
||||
layout(location=2) in vec2 inTexCoord;
|
||||
layout(location=3) in vec4 inColour; // normalized UBYTE -> float
|
||||
layout(location=4) in vec3 inFaceNormal;
|
||||
layout(location=5) in float inFixedShade;
|
||||
|
||||
out vec2 vTexCoord;
|
||||
out vec4 vColor;
|
||||
out float vAlpha;
|
||||
out vec3 vDummy; // keeps attributes alive
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 c = inColour;
|
||||
if (translatorMap != 0) c.rgb *= 16.0;
|
||||
vColor = c;
|
||||
vTexCoord = inTexCoord;
|
||||
vAlpha = c.a;
|
||||
vDummy = inNormal + inFaceNormal + vec3(inFixedShade);
|
||||
gl_Position = projMat * modelMat * inVertex;
|
||||
}
|
||||
)glsl";
|
||||
|
||||
static const char *fragmentShaderR3DSimple = R"glsl(#version 300 es
|
||||
precision mediump float;
|
||||
precision mediump int;
|
||||
|
||||
in vec2 vTexCoord;
|
||||
in vec4 vColor;
|
||||
in float vAlpha;
|
||||
in vec3 vDummy;
|
||||
|
||||
uniform sampler2D tex1;
|
||||
uniform int textureEnabled;
|
||||
uniform int textureAlpha;
|
||||
uniform int alphaTest;
|
||||
uniform int discardAlpha;
|
||||
|
||||
out vec4 oColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 col = vColor;
|
||||
vec4 tex = vec4(1.0);
|
||||
|
||||
if (textureEnabled != 0) {
|
||||
tex = texture(tex1, vTexCoord);
|
||||
if (textureAlpha != 0) {
|
||||
col *= tex;
|
||||
} else {
|
||||
col.rgb *= tex.rgb;
|
||||
}
|
||||
}
|
||||
|
||||
// keep vDummy "used"
|
||||
col.rgb += vDummy * 0.0;
|
||||
|
||||
if (alphaTest != 0 && tex.a < (32.0/255.0)) discard;
|
||||
if (discardAlpha != 0 && col.a < 0.99) discard;
|
||||
|
||||
oColor = col;
|
||||
}
|
||||
)glsl";
|
||||
|
||||
static const char *vertexShaderR3D = R"glsl(#version 300 es
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
@@ -1774,8 +1774,11 @@ void CModel3::Write32(UINT32 addr, UINT32 data)
|
||||
{
|
||||
UINT32 flipped = FLIPENDIAN32(data);
|
||||
TileGen.WriteRegister(addr&0xFF, flipped);
|
||||
if (addr == 0xF118000C)
|
||||
GPU.TilegenDrawFrame(flipped);
|
||||
if (addr == 0xF118000C) {
|
||||
const bool legacyReal3D = m_config["LegacyReal3D"].ValueAsDefault<bool>(false);
|
||||
if (!legacyReal3D)
|
||||
GPU.TilegenDrawFrame(flipped);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2055,9 +2058,10 @@ void CModel3::RunMainBoardFrame(void)
|
||||
unsigned vBlankCycles = lineCycles * 40;
|
||||
unsigned dispCycles = lineCycles * 384;
|
||||
unsigned statusCycles = (unsigned)((float)frameCycles * (0.005f));
|
||||
const bool legacyTiming =
|
||||
m_config["LegacyReal3DTiming"].ValueAsDefault<bool>(
|
||||
m_config["LegacyStatusBit"].ValueAsDefault<bool>(false));
|
||||
const bool legacyTiming =
|
||||
m_config["LegacyReal3D"].ValueAsDefault<bool>(false) ||
|
||||
m_config["LegacyReal3DTiming"].ValueAsDefault<bool>(
|
||||
m_config["LegacyStatusBit"].ValueAsDefault<bool>(false));
|
||||
|
||||
// Scale PPC timer ratio according to speed at which the PowerPC is being emulated so that the observed running frequency of the PPC timer
|
||||
// registers is more or less correct. This is needed to get the Virtua Striker 2 series of games running at the right speed (they are
|
||||
|
||||
+21
-2
@@ -166,16 +166,20 @@ static void UpdateRenderConfig(IRender3D *Render3D, uint64_t internalRenderConfi
|
||||
|
||||
void CReal3D::BeginVBlank(int statusCycles)
|
||||
{
|
||||
const bool legacyReal3D = m_config["LegacyReal3D"].ValueAsDefault<bool>(false);
|
||||
const bool legacyTiming =
|
||||
m_config["LegacyReal3DTiming"].ValueAsDefault<bool>(
|
||||
m_config["LegacyStatusBit"].ValueAsDefault<bool>(false));
|
||||
m_useLegacyStatusBit = legacyTiming;
|
||||
m_useLegacyStatusBit = legacyReal3D || legacyTiming;
|
||||
if (m_useLegacyStatusBit)
|
||||
{
|
||||
statusChange = ppc_total_cycles() + statusCycles;
|
||||
m_evenFrame = !m_evenFrame;
|
||||
}
|
||||
|
||||
if (legacyReal3D)
|
||||
return;
|
||||
|
||||
m_pingPongCopy = m_pingPong;
|
||||
|
||||
if (commandPortWritten)
|
||||
@@ -192,6 +196,8 @@ void CReal3D::EndVBlank(void)
|
||||
|
||||
void CReal3D::FlipPingPongBit(void)
|
||||
{
|
||||
if (m_config["LegacyReal3D"].ValueAsDefault<bool>(false))
|
||||
return;
|
||||
m_pingPong = !m_pingPong;
|
||||
m_tilegenDrawFrame = false;
|
||||
commandPortWritten = false;
|
||||
@@ -199,6 +205,9 @@ void CReal3D::FlipPingPongBit(void)
|
||||
|
||||
uint32_t CReal3D::SyncSnapshots(void)
|
||||
{
|
||||
const bool legacyReal3D = m_config["LegacyReal3D"].ValueAsDefault<bool>(false);
|
||||
if (legacyReal3D)
|
||||
commandPortWritten = false;
|
||||
if (!m_gpuMultiThreaded)
|
||||
return 0;
|
||||
|
||||
@@ -688,6 +697,8 @@ void CReal3D::FlushTextures()
|
||||
|
||||
bool CReal3D::PollPingPong()
|
||||
{
|
||||
if (m_config["LegacyReal3D"].ValueAsDefault<bool>(false))
|
||||
return false;
|
||||
return m_pingPong != m_pingPongCopy;
|
||||
}
|
||||
|
||||
@@ -699,6 +710,8 @@ void CReal3D::DrawFrame()
|
||||
|
||||
void CReal3D::TilegenDrawFrame(uint32_t flags)
|
||||
{
|
||||
if (m_config["LegacyReal3D"].ValueAsDefault<bool>(false))
|
||||
return;
|
||||
(void)flags;
|
||||
m_tilegenDrawFrame = true;
|
||||
|
||||
@@ -714,6 +727,12 @@ void CReal3D::Flush(void)
|
||||
commandPortWritten = true;
|
||||
DebugLog("Real3D 88000000 written @ PC=%08X\n", ppc_get_pc());
|
||||
|
||||
if (m_config["LegacyReal3D"].ValueAsDefault<bool>(false))
|
||||
{
|
||||
FlushTextures();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PollPingPong())
|
||||
{
|
||||
FlushTextures();
|
||||
@@ -838,7 +857,7 @@ uint32_t CReal3D::ReadRegister(unsigned reg)
|
||||
DebugLog("Real3D: Read reg %X\n", reg);
|
||||
if (reg == 0)
|
||||
{
|
||||
if (m_useLegacyStatusBit)
|
||||
if (m_useLegacyStatusBit || m_config["LegacyReal3D"].ValueAsDefault<bool>(false))
|
||||
{
|
||||
uint32_t ping_pong;
|
||||
if (m_evenFrame) {
|
||||
|
||||
@@ -29,8 +29,8 @@ android {
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
|
||||
versionCode = 19
|
||||
versionName = "1.1.8"
|
||||
versionCode = 20
|
||||
versionName = "1.1.9"
|
||||
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a")
|
||||
|
||||
@@ -205,4 +205,17 @@ InputAnalogGunRight = JOY1_XAXIS_POS
|
||||
InputAnalogGunUp = JOY1_YAXIS_NEG
|
||||
InputAnalogGunDown = JOY1_YAXIS_POS
|
||||
|
||||
; Virtual On 2: force simple shader on Android to avoid GPU hangs.
|
||||
[ von2 ]
|
||||
AndroidSimpleShader = 1
|
||||
|
||||
[ von254g ]
|
||||
AndroidSimpleShader = 1
|
||||
|
||||
[ von2a ]
|
||||
AndroidSimpleShader = 1
|
||||
|
||||
[ von2o ]
|
||||
AndroidSimpleShader = 1
|
||||
|
||||
|
||||
|
||||
@@ -197,6 +197,13 @@ static void AndroidTileBlit(const uint32_t* pixelsARGB, int width, int height, b
|
||||
g_presenter->Render(alphaBlend);
|
||||
}
|
||||
|
||||
static bool IsVonGame(const std::string& name)
|
||||
{
|
||||
auto lower = name;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return (char)std::tolower(c); });
|
||||
return lower == "von2" || lower == "von254g" || lower == "von2a" || lower == "von2o";
|
||||
}
|
||||
|
||||
static void GunToViewCoords(float& x, float& y)
|
||||
{
|
||||
x = (x - 150.0f) / (651.0f - 150.0f);
|
||||
@@ -316,6 +323,10 @@ struct Super3Host {
|
||||
config.Set("LegacySoundDSP", false);
|
||||
config.Set("New3DEngine", true);
|
||||
config.Set("New3DAccurate", false);
|
||||
config.Set("AndroidForceNew3D", true);
|
||||
config.Set("AndroidTileBlit", true);
|
||||
config.Set("AndroidSimpleShader", false);
|
||||
config.Set("AndroidClampRamVerts", true);
|
||||
config.Set("QuadRendering", false);
|
||||
config.Set("FlipStereo", false);
|
||||
// The core expects this node to exist (throws std::range_error otherwise).
|
||||
@@ -357,6 +368,20 @@ struct Super3Host {
|
||||
inputSystem.ApplyConfig(config);
|
||||
}
|
||||
|
||||
void ApplyAndroidTileBlit()
|
||||
{
|
||||
const bool enabled = config["AndroidTileBlit"].ValueAsDefault<bool>(true);
|
||||
CRender2D::SetAndroidTileBlit(enabled ? &AndroidTileBlit : nullptr);
|
||||
}
|
||||
|
||||
void ApplyGameHardOverrides(const std::string& gameName)
|
||||
{
|
||||
if (IsVonGame(gameName)) {
|
||||
// Force simple shader path for Virtual On 2 variants to avoid GPU hangs.
|
||||
config.Set("AndroidSimpleShader", true);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyAndroidHardOverrides()
|
||||
{
|
||||
auto ensureKeyboardFallback = [&](const char* cfgKey, const char* defaultKeyToken) {
|
||||
@@ -384,7 +409,9 @@ struct Super3Host {
|
||||
|
||||
// Keep unsupported rendering knobs clamped on Android.
|
||||
config.Set("QuadRendering", false);
|
||||
config.Set("New3DEngine", true);
|
||||
const bool forceNew3D = config["AndroidForceNew3D"].ValueAsDefault<bool>(true);
|
||||
if (forceNew3D)
|
||||
config.Set("New3DEngine", true);
|
||||
config.Set("New3DAccurate", false);
|
||||
|
||||
// Allow user-specified framebuffer sizes. Clamp to sane bounds so we don't
|
||||
@@ -485,6 +512,7 @@ struct Super3Host {
|
||||
}
|
||||
|
||||
ApplyAndroidHardOverrides();
|
||||
ApplyAndroidTileBlit();
|
||||
inputSystem.ApplyConfig(config);
|
||||
}
|
||||
|
||||
@@ -492,6 +520,7 @@ struct Super3Host {
|
||||
ApplyDefaults();
|
||||
ApplyIniOverrides(std::string());
|
||||
ApplyAndroidHardOverrides();
|
||||
ApplyAndroidTileBlit();
|
||||
if (!std::filesystem::exists(gamesXml)) {
|
||||
ErrorLog("Games XML not found at %s", gamesXml.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Games XML not found at %s", gamesXml.c_str());
|
||||
@@ -542,6 +571,8 @@ struct Super3Host {
|
||||
// Apply Supermodel.ini overrides (Global + [ game ]) after the loader has determined game->name.
|
||||
ApplyIniOverrides(game.name);
|
||||
ApplyAndroidHardOverrides();
|
||||
ApplyGameHardOverrides(game.name);
|
||||
ApplyAndroidTileBlit();
|
||||
|
||||
// Initialize inputs before attaching to model
|
||||
if (!inputs.Initialize()) {
|
||||
@@ -977,7 +1008,6 @@ extern "C" int SDL_main(int argc, char* argv[]) {
|
||||
|
||||
Super3Host host;
|
||||
g_presenter = &presenter;
|
||||
CRender2D::SetAndroidTileBlit(&AndroidTileBlit);
|
||||
g_host = &host;
|
||||
// Initialize renderer backends up-front. The core will attach VRAM/palette/register
|
||||
// pointers later (after it has initialized the tile generator).
|
||||
|
||||
@@ -120,7 +120,39 @@ object AssetInstaller {
|
||||
changed = true
|
||||
}
|
||||
|
||||
fun ensureSectionKey(sectionName: String, key: String, value: String) {
|
||||
val sectionIdx =
|
||||
out.indexOfFirst { line ->
|
||||
val t = line.trim()
|
||||
if (!t.startsWith("[") || !t.endsWith("]")) return@indexOfFirst false
|
||||
val name = t.removePrefix("[").removeSuffix("]").trim()
|
||||
name.equals(sectionName, ignoreCase = true)
|
||||
}
|
||||
if (sectionIdx < 0) {
|
||||
if (out.isNotEmpty() && out.last().isNotBlank()) out.add("")
|
||||
out.add("[ $sectionName ]")
|
||||
out.add("$key = $value")
|
||||
changed = true
|
||||
return
|
||||
}
|
||||
|
||||
val endIdx = (sectionIdx + 1 + out.drop(sectionIdx + 1).indexOfFirst { it.trim().startsWith("[") })
|
||||
.let { if (it <= sectionIdx) out.size else it }
|
||||
|
||||
val hasKey = out.subList(sectionIdx + 1, endIdx).any {
|
||||
it.trim().startsWith("$key", ignoreCase = true)
|
||||
}
|
||||
if (hasKey) return
|
||||
|
||||
out.add(endIdx, "$key = $value")
|
||||
changed = true
|
||||
}
|
||||
|
||||
ensureKey("[ Global ]", "LegacyReal3DTiming", "1")
|
||||
ensureSectionKey("von2", "AndroidSimpleShader", "1")
|
||||
ensureSectionKey("von254g", "AndroidSimpleShader", "1")
|
||||
ensureSectionKey("von2a", "AndroidSimpleShader", "1")
|
||||
ensureSectionKey("von2o", "AndroidSimpleShader", "1")
|
||||
|
||||
if (!changed) return
|
||||
runCatching { ini.writeText(out.joinToString(System.lineSeparator())) }
|
||||
|
||||
@@ -366,9 +366,6 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
AssetInstaller.ensureInstalled(this, internalUserRoot())
|
||||
|
||||
applyVideoSettingsToIni(internalUserRoot(), loadVideoSettings())
|
||||
applyTimingDefaultToIni(internalUserRoot(), loadTimingPref())
|
||||
|
||||
applyViewMode(viewMode)
|
||||
refreshUi()
|
||||
pendingRomLaunch?.let { info ->
|
||||
@@ -1162,8 +1159,6 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
AssetInstaller.ensureInstalled(this, internalRoot)
|
||||
applyVideoSettingsToIni(internalRoot, loadVideoSettings())
|
||||
applyTimingDefaultToIni(internalRoot, loadTimingPref())
|
||||
|
||||
val cacheDir = File(internalRoot, "romcache")
|
||||
val required = gameDef?.let { resolveRequiredRomZips(it) } ?: listOf(info.baseName)
|
||||
@@ -1294,8 +1289,6 @@ class MainActivity : AppCompatActivity() {
|
||||
UserDataSync.syncFromTreeIntoInternal(this, userUri, internalRoot, UserDataSync.DIRS_GAME_SYNC)
|
||||
}
|
||||
|
||||
applyVideoSettingsToIni(internalRoot, loadVideoSettings())
|
||||
|
||||
val cacheDir = File(internalRoot, "romcache")
|
||||
val required = resolveRequiredRomZips(game)
|
||||
val missing = required.filter { !zipDocs.containsKey(it) }
|
||||
|
||||
@@ -59,6 +59,7 @@ object UserDataSync {
|
||||
}
|
||||
|
||||
private fun copyDocFileToFile(resolver: ContentResolver, from: DocumentFile, to: File) {
|
||||
if (!shouldCopyDocToFile(from, to)) return
|
||||
val uri = from.uri
|
||||
resolver.openInputStream(uri)?.use { input ->
|
||||
to.parentFile?.mkdirs()
|
||||
@@ -82,6 +83,7 @@ object UserDataSync {
|
||||
|
||||
private fun copyFileToDocFile(resolver: ContentResolver, from: File, toDir: DocumentFile) {
|
||||
val existing = toDir.findFile(from.name)
|
||||
if (!shouldCopyFileToDoc(from, existing)) return
|
||||
val outDoc = existing ?: toDir.createFile("application/octet-stream", from.name) ?: return
|
||||
resolver.openOutputStream(outDoc.uri)?.use { output ->
|
||||
from.inputStream().use { input ->
|
||||
@@ -89,4 +91,30 @@ object UserDataSync {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldCopyDocToFile(from: DocumentFile, to: File): Boolean {
|
||||
if (!to.exists()) return true
|
||||
val srcMod = from.lastModified()
|
||||
val dstMod = to.lastModified()
|
||||
if (srcMod > 0L && dstMod > 0L) return srcMod > dstMod
|
||||
if (srcMod > 0L && dstMod == 0L) return true
|
||||
if (srcMod == 0L && dstMod > 0L) return false
|
||||
val srcLen = from.length()
|
||||
val dstLen = to.length()
|
||||
if (srcLen > 0L && dstLen > 0L) return srcLen != dstLen
|
||||
return false
|
||||
}
|
||||
|
||||
private fun shouldCopyFileToDoc(from: File, to: DocumentFile?): Boolean {
|
||||
if (to == null || !to.exists()) return true
|
||||
val srcMod = from.lastModified()
|
||||
val dstMod = to.lastModified()
|
||||
if (srcMod > 0L && dstMod > 0L) return srcMod > dstMod
|
||||
if (srcMod > 0L && dstMod == 0L) return true
|
||||
if (srcMod == 0L && dstMod > 0L) return false
|
||||
val srcLen = from.length()
|
||||
val dstLen = to.length()
|
||||
if (srcLen > 0L && dstLen > 0L) return srcLen != dstLen
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user