Dev #2
No files matched your search
Generated
+3
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
Generated
+1
@@ -0,0 +1 @@
|
||||
GaCy-Android
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+123
@@ -0,0 +1,123 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+19
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+17
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,6 +1,6 @@
|
||||
# GaCy Android
|
||||
|
||||
[ภาษาไทย](README.md) | English | [简体中文](README_ZH.md)
|
||||
[ภาษาไทย](./README.md) | English | [简体中文](./README_ZH.md)
|
||||
|
||||
GaCy Android is an Android companion and local server manager for the
|
||||
GaC-hkrpg-go project. The
|
||||
@@ -1,6 +1,6 @@
|
||||
# GaCy Android
|
||||
|
||||
[ภาษาไทย](README.md) | [English](README_EN.md) | 简体中文
|
||||
[ภาษาไทย](README.md) | [English](./README_EN.md) | 简体中文
|
||||
|
||||
GaCy Android 是 GaC-hkrpg-go
|
||||
的 Android 辅助应用和本地服务器管理器。移植仍在进行中:应用可通过前台服务启动
|
||||
@@ -1,12 +1,8 @@
|
||||
# 📱 GaCy Android
|
||||
|
||||
> **Go port in progress:** the local server now launches the SDK and game
|
||||
> processes built from GaC-hkrpg-go. Teleporting uses the game's own map;
|
||||
> the older ppSR character settings are not yet connected. On 1 November 2026,
|
||||
> the user confirmed that an ARM64 debug build could enter the game and teleport
|
||||
> using the in-game map on the same phone. On 2 November, the user confirmed
|
||||
> that the updated build started the server, entered the game and restarted the
|
||||
> server successfully. Other features still need validation.
|
||||
> processes built from GaCy-go. Teleporting uses the game's own map;
|
||||
> Other features still need validation.
|
||||
|
||||
The Go SDK, shared-data, game-server and KCP package tests have also passed;
|
||||
these automated tests do not establish that every gameplay feature works on
|
||||
@@ -56,7 +52,7 @@ Built with **Kotlin**, **Jetpack Compose**, and **Material Design 3**.
|
||||
[](https://developer.android.com/ndk)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
[ภาษาไทย](README.md) | [English](README_EN.md) | [简体中文](README_ZH.md)
|
||||
ภาษาไทย | [English](./Docs/README_EN.md) | [简体中文](./Docs/README_ZH.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"assets": {
|
||||
"bootstrap-native/arm64-v8a/_struct.cpython-314-aarch64-linux-android.so": "9b083e7b97e92291f73d80d7495c9cee8351596b",
|
||||
"requirements-arm64-v8a.imy": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33",
|
||||
"bootstrap-native/arm64-v8a/java/chaquopy.so": "1284a42a1b74c8fc5c8705a4f6f309134ecef4a8",
|
||||
"bootstrap.imy": "6c6d7c85e4764e96924001f9d6290501bfbfc704",
|
||||
"bootstrap-native/arm64-v8a/math.cpython-314-aarch64-linux-android.so": "dc859c93d3fc01f2d087471052895f230f732436",
|
||||
"cacert.pem": "fec7b8c0a17c1c555fd33baee53678f31b7645ee",
|
||||
"bootstrap-native/arm64-v8a/_bz2.cpython-314-aarch64-linux-android.so": "b48b9abf1f87d587a5f5beb4b38bb17acf29c4d3",
|
||||
"bootstrap-native/arm64-v8a/_ctypes.cpython-314-aarch64-linux-android.so": "50a6a36dab6a9ebe4fca5797872e8d2c3538db56",
|
||||
"bootstrap-native/arm64-v8a/mmap.cpython-314-aarch64-linux-android.so": "b571492fdceac1279bedc8163e27ac205e104b14",
|
||||
"app.imy": "8e998f6e20dc7152ab075f8edf0644d411cd7f76",
|
||||
"stdlib-common.imy": "8715635411fe145000fbf3b7ce4c3a085646d300",
|
||||
"bootstrap-native/arm64-v8a/_random.cpython-314-aarch64-linux-android.so": "1c6c12f9f43546da0e108031f824b7fd6e79a36a",
|
||||
"bootstrap-native/arm64-v8a/_lzma.cpython-314-aarch64-linux-android.so": "6f3f86f607b4d775452c128858fa26d19c7da56a",
|
||||
"bootstrap-native/arm64-v8a/zlib.cpython-314-aarch64-linux-android.so": "2d164f648a0f97c1d8710d41cb66754e9daff44e",
|
||||
"bootstrap-native/arm64-v8a/binascii.cpython-314-aarch64-linux-android.so": "ff4d2c47c081af6518c8a6a6fa07edcc077b1b6b",
|
||||
"stdlib-arm64-v8a.imy": "ebebf5dc919d7e686936904604fe9a29fede81e9",
|
||||
"requirements-common.imy": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33"
|
||||
},
|
||||
"extract_packages": [],
|
||||
"python_version": "3.14"
|
||||
}
|
||||
"assets": {
|
||||
"bootstrap-native/arm64-v8a/_struct.cpython-314-aarch64-linux-android.so": "9b083e7b97e92291f73d80d7495c9cee8351596b",
|
||||
"requirements-arm64-v8a.imy": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33",
|
||||
"bootstrap-native/arm64-v8a/java/chaquopy.so": "1284a42a1b74c8fc5c8705a4f6f309134ecef4a8",
|
||||
"bootstrap.imy": "6c6d7c85e4764e96924001f9d6290501bfbfc704",
|
||||
"bootstrap-native/arm64-v8a/math.cpython-314-aarch64-linux-android.so": "dc859c93d3fc01f2d087471052895f230f732436",
|
||||
"cacert.pem": "fec7b8c0a17c1c555fd33baee53678f31b7645ee",
|
||||
"bootstrap-native/arm64-v8a/_bz2.cpython-314-aarch64-linux-android.so": "b48b9abf1f87d587a5f5beb4b38bb17acf29c4d3",
|
||||
"bootstrap-native/arm64-v8a/_ctypes.cpython-314-aarch64-linux-android.so": "50a6a36dab6a9ebe4fca5797872e8d2c3538db56",
|
||||
"bootstrap-native/arm64-v8a/mmap.cpython-314-aarch64-linux-android.so": "b571492fdceac1279bedc8163e27ac205e104b14",
|
||||
"app.imy": "fea5d90dd93b0e7264079d53d95d61e7a6f232c0",
|
||||
"stdlib-common.imy": "8715635411fe145000fbf3b7ce4c3a085646d300",
|
||||
"bootstrap-native/arm64-v8a/_random.cpython-314-aarch64-linux-android.so": "1c6c12f9f43546da0e108031f824b7fd6e79a36a",
|
||||
"bootstrap-native/arm64-v8a/_lzma.cpython-314-aarch64-linux-android.so": "6f3f86f607b4d775452c128858fa26d19c7da56a",
|
||||
"bootstrap-native/arm64-v8a/zlib.cpython-314-aarch64-linux-android.so": "2d164f648a0f97c1d8710d41cb66754e9daff44e",
|
||||
"bootstrap-native/arm64-v8a/binascii.cpython-314-aarch64-linux-android.so": "ff4d2c47c081af6518c8a6a6fa07edcc077b1b6b",
|
||||
"stdlib-arm64-v8a.imy": "ebebf5dc919d7e686936904604fe9a29fede81e9",
|
||||
"requirements-common.imy": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33"
|
||||
},
|
||||
"extract_packages": [],
|
||||
"python_version": "3.14"
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -43,16 +43,41 @@ class UpdateRepository(private val context: Context) {
|
||||
|
||||
fun isServerInstalled(): Boolean {
|
||||
val ppsrFile = File(context.filesDir, "server/build/build/ppsr.pyc")
|
||||
return ppsrFile.exists() && ppsrFile.length() > 0
|
||||
if (!ppsrFile.exists() || ppsrFile.length() < 16) return false
|
||||
|
||||
// Check if build code matches current APK
|
||||
val installedBuildCode = prefs.getInt("installed_build_code", -1)
|
||||
if (installedBuildCode != com.gacy.android.BuildConfig.VERSION_CODE) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate that python bytecode magic is genuine Python 3.14: [43, 14, 13, 10]
|
||||
try {
|
||||
ppsrFile.inputStream().use { stream ->
|
||||
val magic = ByteArray(4)
|
||||
if (stream.read(magic) == 4) {
|
||||
if (magic[0] != 43.toByte() || magic[1] != 14.toByte() || magic[2] != 13.toByte() || magic[3] != 10.toByte()) {
|
||||
return false // Incompatible bytecode from old install, force refresh!
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun ensureServerInstalled(): Boolean = withContext(Dispatchers.IO) {
|
||||
suspend fun ensureServerInstalled(force: Boolean = false): Boolean = withContext(Dispatchers.IO) {
|
||||
val ppsrFile = File(context.filesDir, "server/build/build/ppsr.pyc")
|
||||
if (ppsrFile.exists() && ppsrFile.length() > 0) {
|
||||
if (!force && isServerInstalled()) {
|
||||
return@withContext true
|
||||
}
|
||||
|
||||
try {
|
||||
com.gacy.android.CrashTracker.breadcrumb(context, "UpdateRepository: ensureServerInstalled start (force=$force)")
|
||||
val stagingDir = File(context.filesDir, "server/staging")
|
||||
val targetServerDir = File(context.filesDir, "server/build/build")
|
||||
|
||||
@@ -60,6 +85,7 @@ class UpdateRepository(private val context: Context) {
|
||||
stagingDir.mkdirs()
|
||||
|
||||
// 1. Extract build.zip from bundled assets
|
||||
com.gacy.android.CrashTracker.breadcrumb(context, "UpdateRepository: extracting build.zip")
|
||||
extractAssetZip("build.zip", stagingDir)
|
||||
|
||||
val stagedBuildDir = File(stagingDir, "build")
|
||||
@@ -67,12 +93,25 @@ class UpdateRepository(private val context: Context) {
|
||||
|
||||
// 2. Extract runtime packages into effective directory
|
||||
try {
|
||||
com.gacy.android.CrashTracker.breadcrumb(context, "UpdateRepository: extracting packages.zip")
|
||||
extractAssetZip("packages.zip", effectiveStagedDir)
|
||||
} catch (e: Exception) {
|
||||
com.gacy.android.CrashTracker.breadcrumb(context, "UpdateRepository: packages.zip notice: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
// Keep the player's settings when refreshing the bundled server.
|
||||
for (name in listOf("persistent.json", "config.json")) {
|
||||
val existing = File(targetServerDir, "misc/$name")
|
||||
if (existing.isFile) {
|
||||
val destination = File(effectiveStagedDir, "misc/$name")
|
||||
destination.parentFile?.mkdirs()
|
||||
existing.copyTo(destination, overwrite = true)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Move/Rename into place
|
||||
com.gacy.android.CrashTracker.breadcrumb(context, "UpdateRepository: moving to target directory")
|
||||
if (targetServerDir.exists()) {
|
||||
targetServerDir.deleteRecursively()
|
||||
}
|
||||
@@ -85,9 +124,16 @@ class UpdateRepository(private val context: Context) {
|
||||
}
|
||||
if (stagingDir.exists()) stagingDir.deleteRecursively()
|
||||
|
||||
setInstalledVersion("4.6.53-embedded")
|
||||
prefs.edit()
|
||||
.putInt("installed_build_code", com.gacy.android.BuildConfig.VERSION_CODE)
|
||||
.putString(PREF_INSTALLED_VERSION, "4.6.53-embedded-py314")
|
||||
.apply()
|
||||
|
||||
setInstalledVersion("4.6.53-embedded-py314")
|
||||
com.gacy.android.CrashTracker.breadcrumb(context, "UpdateRepository: ensureServerInstalled finished success=${ppsrFile.exists()}")
|
||||
return@withContext ppsrFile.exists()
|
||||
} catch (e: Exception) {
|
||||
com.gacy.android.CrashTracker.breadcrumb(context, "UpdateRepository: ensureServerInstalled error: ${e.message}")
|
||||
e.printStackTrace()
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
+520
-42
@@ -8,6 +8,12 @@ import socket
|
||||
import struct
|
||||
import warnings
|
||||
|
||||
# === SYS.EXIT INTERCEPTOR (Prevent Android Process Termination) ===
|
||||
def _safe_sys_exit(code=0):
|
||||
print(f"[Sys] Intercepted sys.exit({code}), prevented Android process exit.")
|
||||
raise SystemExit(code)
|
||||
sys.exit = _safe_sys_exit
|
||||
|
||||
# === GLOBAL WARNING SUPPRESSION & METADATA SHIM (very early) ===
|
||||
try:
|
||||
import importlib.metadata
|
||||
@@ -288,10 +294,14 @@ def _sanitize_persistent_file(persistent_file):
|
||||
data[k][sub_k] = copy.deepcopy(sub_v)
|
||||
modified = True
|
||||
|
||||
if "send_hotfix" not in data:
|
||||
data["send_hotfix"] = True
|
||||
modified = True
|
||||
|
||||
if modified or not os.path.exists(persistent_file):
|
||||
with open(persistent_file, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=4)
|
||||
print("[Patch] persistent.json validated and auto-healed with complete avatar/scene defaults")
|
||||
print("[Patch] persistent.json validated and auto-healed with complete avatar/scene defaults (send_hotfix=True)")
|
||||
except Exception as e:
|
||||
print(f"[Patch Warning] sanitize persistent: {e}")
|
||||
|
||||
@@ -332,32 +342,7 @@ def kill_server_process():
|
||||
pass
|
||||
_active_server_instance = None
|
||||
|
||||
try:
|
||||
for obj in gc.get_objects():
|
||||
if isinstance(obj, socket.socket):
|
||||
try:
|
||||
obj.shutdown(socket.SHUT_RDWR)
|
||||
obj.close()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
time.sleep(1.2)
|
||||
|
||||
for _ in range(5):
|
||||
try:
|
||||
_thread.interrupt_main()
|
||||
time.sleep(0.2)
|
||||
except Exception:
|
||||
pass
|
||||
# Do not close unrelated application sockets or interrupt the Android main thread.
|
||||
|
||||
# --- 5. Main Execution Entrypoint ---
|
||||
def run_server(ppsr_path, activity_context=None):
|
||||
@@ -458,13 +443,183 @@ def run_server(ppsr_path, activity_context=None):
|
||||
except Exception as e:
|
||||
print(f"[Patch Warning] Manager persistent patch: {e}")
|
||||
|
||||
# --- 4.6 Command IDs & Router Patch ---
|
||||
CORE_46 = {
|
||||
"ChangeLineupLeaderCsReq": 798,
|
||||
"ChangeLineupLeaderScRsp": 747,
|
||||
"ClientDownloadDataScNotify": 60,
|
||||
"DressAvatarSkinCsReq": 357,
|
||||
"DressAvatarSkinScRsp": 382,
|
||||
"GetAllLineupDataCsReq": 704,
|
||||
"GetAllLineupDataScRsp": 794,
|
||||
"GetAssistListCsReq": 2962,
|
||||
"GetAssistListScRsp": 2980,
|
||||
"GetAvatarDataCsReq": 374,
|
||||
"GetAvatarDataScRsp": 319,
|
||||
"GetBagCsReq": 574,
|
||||
"GetBagScRsp": 519,
|
||||
"GetBasicInfoCsReq": 46,
|
||||
"GetBasicInfoScRsp": 20,
|
||||
"GetBigDataAllRecommendCsReq": 2437,
|
||||
"GetBigDataAllRecommendScRsp": 2411,
|
||||
"GetCurLineupDataCsReq": 731,
|
||||
"GetCurLineupDataScRsp": 787,
|
||||
"GetCurSceneInfoCsReq": 1459,
|
||||
"GetCurSceneInfoScRsp": 1418,
|
||||
"GetFriendAssistListCsReq": 2978,
|
||||
"GetFriendAssistListScRsp": 2939,
|
||||
"GetFriendListInfoCsReq": 2974,
|
||||
"GetFriendListInfoScRsp": 2919,
|
||||
"GetMissionStatusCsReq": 1204,
|
||||
"GetMissionStatusScRsp": 1294,
|
||||
"GetRechargeGiftInfoCsReq": 8375,
|
||||
"GetRechargeGiftInfoScRsp": 8368,
|
||||
"GetUnreleasedBlockInfoCsReq": 4190,
|
||||
"GetUnreleasedBlockInfoScRsp": 4112,
|
||||
"JoinLineupCsReq": 705,
|
||||
"JoinLineupScRsp": 771,
|
||||
"PVEBattleResultCsReq": 174,
|
||||
"PVEBattleResultScRsp": 119,
|
||||
"PlayerGetTokenCsReq": 5,
|
||||
"PlayerGetTokenScRsp": 71,
|
||||
"PlayerHeartBeatCsReq": 45,
|
||||
"PlayerHeartBeatScRsp": 16,
|
||||
"PlayerLoginCsReq": 74,
|
||||
"PlayerLoginFinishCsReq": 9,
|
||||
"PlayerLoginFinishScRsp": 13,
|
||||
"PlayerLoginScRsp": 19,
|
||||
"PlayerSyncScNotify": 674,
|
||||
"QuitLineupCsReq": 759,
|
||||
"QuitLineupScRsp": 718,
|
||||
"ReplaceLineupCsReq": 782,
|
||||
"ReplaceLineupScRsp": 715,
|
||||
"SceneCastSkillCsReq": 1405,
|
||||
"SceneCastSkillScRsp": 1471,
|
||||
"SceneEntityMoveCsReq": 1474,
|
||||
"SceneEntityMoveScNotify": 1427,
|
||||
"SceneEntityMoveScRsp": 1419,
|
||||
"SceneGroupRefreshScNotify": 1434,
|
||||
"SetAvatarEnhancedIdCsReq": 381,
|
||||
"SetAvatarEnhancedIdScRsp": 322,
|
||||
"StartCocoonStageCsReq": 1444,
|
||||
"StartCocoonStageScRsp": 1481,
|
||||
"SyncLineupNotify": 727,
|
||||
"TakeOffAvatarSkinCsReq": 315,
|
||||
"TakeOffAvatarSkinScRsp": 323
|
||||
}
|
||||
|
||||
try:
|
||||
import json
|
||||
import game.cmdid as _cmdid_mod
|
||||
import game.router as _router_mod
|
||||
_orig_router_handle = _router_mod.handle
|
||||
import game.packet as _packet_mod
|
||||
|
||||
TABLE_46 = dict(CORE_46)
|
||||
for json_path in [
|
||||
os.path.join(ppsr_dir, "cmd_ids_4_6.json"),
|
||||
os.path.join(misc_dir, "cmd_ids_4_6.json"),
|
||||
r"/data/data/com.gacy.android/files/server/cmd_ids_4_6.json",
|
||||
r"/data/data/com.gacy.android/files/server/build/build/cmd_ids_4_6.json"
|
||||
]:
|
||||
if os.path.exists(json_path):
|
||||
try:
|
||||
with open(json_path, "r", encoding="utf-8") as _cjf:
|
||||
_raw = json.load(_cjf)
|
||||
for _k, _v in _raw.items():
|
||||
_name = _k[3:] if _k.startswith("Cmd") else _k
|
||||
TABLE_46[_name] = _v
|
||||
if "GetFriendListInfoScRsp" not in TABLE_46:
|
||||
TABLE_46["GetFriendListInfoScRsp"] = 2919
|
||||
print(f"[Patch] Loaded {len(TABLE_46)} 4.6 commands from {json_path}")
|
||||
break
|
||||
except Exception as _cje:
|
||||
print(f"[Patch Warning] Load cmd_ids_4_6.json: {_cje}")
|
||||
|
||||
_cmdid_mod.cmd_dict = TABLE_46
|
||||
_router_mod.cmd_dict = TABLE_46
|
||||
_router_mod.cmds = list(TABLE_46.keys())
|
||||
_router_mod.cmd_ids = list(TABLE_46.values())
|
||||
|
||||
_handlers_to_wire = [
|
||||
('GetAvatarDataCsReq', 'GetAvatarDataCsReq'),
|
||||
('DressAvatarSkinCsReq', 'DressAvatarSkinCsReq'),
|
||||
('TakeOffAvatarSkinCsReq', 'TakeOffAvatarSkinCsReq'),
|
||||
('SetAvatarEnhancedIdCsReq', 'SetAvatarEnhancedIdCsReq'),
|
||||
('PVEBattleResultCsReq', 'PVEBattleResultCsReq'),
|
||||
('GetBagCsReq', 'GetBagCsReq'),
|
||||
('GetCurLineupDataCsReq', 'GetCurLineupDataCsReq'),
|
||||
('JoinLineupCsReq', 'JoinLineupCsReq'),
|
||||
('QuitLineupCsReq', 'QuitLineupCsReq'),
|
||||
('ChangeLineupLeaderCsReq', 'ChangeLineupLeaderCsReq'),
|
||||
('GetAllLineupDataCsReq', 'GetAllLineupDataCsReq'),
|
||||
('ReplaceLineupCsReq', 'ReplaceLineupCsReq'),
|
||||
('GetUnreleasedBlockInfoCsReq', 'GetUnreleasedBlockInfoCsReq'),
|
||||
('GetMissionStatusCsReq', 'GetMissionStatusCsReq'),
|
||||
('PlayerLoginCsReq', 'PlayerLoginCsReq'),
|
||||
('PlayerGetTokenCsReq', 'PlayerGetTokenCsReq'),
|
||||
('GetBasicInfoCsReq', 'GetBasicInfoCsReq'),
|
||||
('PlayerHeartBeatCsReq', 'PlayerHeartBeatCsReq'),
|
||||
('PlayerLoginFinishCsReq', 'PlayerLoginFinishCsReq'),
|
||||
('GetRechargeGiftInfoCsReq', 'GetRechargeGiftInfoCsReq'),
|
||||
('GetBigDataAllRecommendCsReq', 'GetBigDataAllRecommendCsReq'),
|
||||
('SceneCastSkillCsReq', 'SceneCastSkillCsReq'),
|
||||
('GetCurSceneInfoCsReq', 'GetCurSceneInfoCsReq'),
|
||||
('StartCocoonStageCsReq', 'StartCocoonStageCsReq'),
|
||||
('SceneEntityMoveCsReq', 'SceneEntityMoveCsReq'),
|
||||
]
|
||||
|
||||
_new_handle_funct = {}
|
||||
for req_name, handler_name in _handlers_to_wire:
|
||||
if hasattr(_router_mod, handler_name):
|
||||
h_mod = getattr(_router_mod, handler_name)
|
||||
if hasattr(h_mod, 'handle'):
|
||||
cid = TABLE_46.get(req_name)
|
||||
if cid is not None:
|
||||
_new_handle_funct[cid] = h_mod.handle
|
||||
|
||||
_router_mod.handle_funct = _new_handle_funct
|
||||
|
||||
def _safe_to_rsp(req_name):
|
||||
if req_name.endswith('CsReq'):
|
||||
rsp_name = req_name[:-5] + 'ScRsp'
|
||||
if rsp_name in TABLE_46:
|
||||
return TABLE_46[rsp_name]
|
||||
return -1
|
||||
|
||||
_router_mod.to_rsp = _safe_to_rsp
|
||||
|
||||
def _safe_router_handle(data):
|
||||
try:
|
||||
return _orig_router_handle(data)
|
||||
cmd, head, body = _packet_mod.read_packet(data)
|
||||
if cmd not in _router_mod.cmd_ids:
|
||||
print(f"[Packet Warning] Unhandled cmd_id {cmd}")
|
||||
return b''
|
||||
|
||||
cmd_name = _router_mod.cmds[_router_mod.cmd_ids.index(cmd)]
|
||||
rsp_cmd = _router_mod.to_rsp(cmd_name)
|
||||
|
||||
if cmd not in _router_mod.handle_funct:
|
||||
if rsp_cmd != -1:
|
||||
print(f"[Packet] Recv dummy {cmd_name} ({cmd}) -> Send dummy {rsp_cmd}")
|
||||
return _packet_mod.write_packet(rsp_cmd, b'', b'')
|
||||
else:
|
||||
print(f"[Packet] Recv notify/unknown {cmd_name} ({cmd})")
|
||||
return b''
|
||||
|
||||
handler_funct = _router_mod.handle_funct[cmd]
|
||||
rsp_body = handler_funct(body)
|
||||
|
||||
if cmd == TABLE_46.get('StartCocoonStageCsReq'):
|
||||
try:
|
||||
import game.services.manager as _mgr
|
||||
_mgr.Manager.load_persistent()
|
||||
_mgr.Manager.load_freesr()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"[Packet] Recv {cmd_name} ({cmd}) -> Send {rsp_cmd}")
|
||||
return _packet_mod.write_packet(rsp_cmd, b'', rsp_body)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"[Router Warning] Handled exception in router.handle: {e}")
|
||||
@@ -472,24 +627,347 @@ def run_server(ppsr_path, activity_context=None):
|
||||
return b''
|
||||
|
||||
_router_mod.handle = _safe_router_handle
|
||||
print("[Patch] game.router.handle wrapped with crash-guard")
|
||||
print(f"[Patch] game.router mapped with {len(_new_handle_funct)} 4.6 handlers and crash-guard")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Patch Warning] Router crash-guard: {e}")
|
||||
|
||||
print(f"Loading bytecode target: {ppsr_path}...")
|
||||
|
||||
with open(ppsr_path, "rb") as f:
|
||||
code_bytes = f.read()
|
||||
print(f"[Patch Warning] 4.6 router patch: {e}")
|
||||
|
||||
# --- SafeHotfixDict & Dispatch Patch (Prevent 1001_3 KeyError & reachable Gateway IP) ---
|
||||
try:
|
||||
import marshal
|
||||
code_obj = marshal.loads(code_bytes[16:])
|
||||
except Exception:
|
||||
code_obj = compile(code_bytes, ppsr_path, "exec")
|
||||
import json
|
||||
import base64
|
||||
import dispatch.dispatchserver as _ds_mod
|
||||
|
||||
class SafeHotfixDict(dict):
|
||||
def __getitem__(self, key):
|
||||
if key in self:
|
||||
return super().__getitem__(key)
|
||||
base = str(key).split('_')[0]
|
||||
if base in self:
|
||||
print(f"[SafeHotfix] Version resolved: {key} -> {base}")
|
||||
return super().__getitem__(base)
|
||||
for cand in ['OSBETAAndroid4.6.54', 'OSBETAAndroid4.6.53', 'OSBETAWin4.6.54', 'OSBETAWin4.6.53', 'OSBETAAndroid4.6.51', 'OSBETAWin4.6.51']:
|
||||
if cand in self:
|
||||
print(f"[SafeHotfix] Version fallback: {key} -> {cand}")
|
||||
return super().__getitem__(cand)
|
||||
if len(self) > 0:
|
||||
first_val = next(iter(self.values()))
|
||||
print(f"[SafeHotfix] Unknown version {key}, falling back to default hotfix.")
|
||||
return first_val
|
||||
return {}
|
||||
|
||||
if hasattr(_ds_mod, 'hotfix') and isinstance(_ds_mod.hotfix, dict):
|
||||
_ds_mod.hotfix = SafeHotfixDict(_ds_mod.hotfix)
|
||||
elif os.path.exists(os.path.join(misc_dir, "hotfix.json")):
|
||||
with open(os.path.join(misc_dir, "hotfix.json"), "r", encoding="utf-8") as _hf_file:
|
||||
_ds_mod.hotfix = SafeHotfixDict(json.load(_hf_file))
|
||||
|
||||
if hasattr(_ds_mod, 'check_hotfix'):
|
||||
_orig_check_hotfix = _ds_mod.check_hotfix
|
||||
def _safe_check_hotfix(version, full_path):
|
||||
try:
|
||||
return _orig_check_hotfix(version, full_path)
|
||||
except Exception as ex:
|
||||
print(f"[SafeHotfix Warning] check_hotfix handled safely: {ex}")
|
||||
_ds_mod.check_hotfix = _safe_check_hotfix
|
||||
|
||||
# Keep _ds_mod.HOST = "127.0.0.1" for internal references
|
||||
_ds_mod.HOST = "127.0.0.1"
|
||||
|
||||
def resolve_client_gate_ip():
|
||||
try:
|
||||
from flask import request
|
||||
if request.remote_addr in ('127.0.0.1', '::1', 'localhost'):
|
||||
return '127.0.0.1'
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as probe:
|
||||
probe.connect((request.remote_addr, 9))
|
||||
return probe.getsockname()[0]
|
||||
except Exception:
|
||||
pass
|
||||
return '127.0.0.1'
|
||||
|
||||
def _patched_query_gateway():
|
||||
from flask import request
|
||||
version = request.args.get('version')
|
||||
if version is None or request.method == 'HEAD':
|
||||
return '', 200
|
||||
|
||||
from msg import classes_pb2
|
||||
gate_ip = resolve_client_gate_ip()
|
||||
print(f"[Gateway] Resolved gateserver target: {gate_ip}:{_ds_mod.GAME_PORT} for client {request.remote_addr}")
|
||||
|
||||
response = classes_pb2.Gateserver(
|
||||
ip=gate_ip,
|
||||
port=_ds_mod.GAME_PORT,
|
||||
unk1=True, unk2=True, unk3=True, unk4=True, unk5=True,
|
||||
unk6=True, unk7=True, unk8=True
|
||||
)
|
||||
|
||||
# Check if persistent specifies send_hotfix (Default to True for 4.6)
|
||||
should_send_hotfix = True
|
||||
try:
|
||||
import game.services.manager as _mgr_mod
|
||||
if hasattr(_mgr_mod, 'Manager') and hasattr(_mgr_mod.Manager, 'persistent'):
|
||||
should_send_hotfix = bool(_mgr_mod.Manager.persistent.get('send_hotfix', True))
|
||||
except Exception:
|
||||
should_send_hotfix = True
|
||||
|
||||
if should_send_hotfix:
|
||||
clean_ver = version.split('_')[0] if version else version
|
||||
hf = _ds_mod.hotfix.get(version) or _ds_mod.hotfix.get(clean_ver)
|
||||
|
||||
if isinstance(hf, dict) and hf:
|
||||
response.asset_bundle_url = str(hf.get('asset_bundle_url', ''))
|
||||
response.asset_bundle_url_b = str(hf.get('asset_bundle_url_b', ''))
|
||||
response.ex_resource_url = str(hf.get('ex_resource_url', ''))
|
||||
response.lua_url = str(hf.get('lua_url', ''))
|
||||
response.ifix_url = str(hf.get('ifix_url', ''))
|
||||
response.lua_version = str(hf.get('lua_version', '0'))
|
||||
response.ifix_version = str(hf.get('ifix_version', '0'))
|
||||
print(f"[Gateway] Hotfix populated for {version} (asset_bundle_url={response.asset_bundle_url[:35]}..., lua_ver={response.lua_version})")
|
||||
else:
|
||||
print(f"[Gateway Warning] No hotfix metadata found for {version}")
|
||||
return 'Unknown client version', 400
|
||||
else:
|
||||
print(f"[Gateway] Hotfix download bypassed (send_hotfix=False) for client {request.remote_addr}")
|
||||
|
||||
return base64.b64encode(response.SerializeToString())
|
||||
|
||||
def _patched_query_dispatch():
|
||||
from flask import request
|
||||
from msg import classes_pb2
|
||||
gate_ip = resolve_client_gate_ip()
|
||||
print(f"[Dispatch] Query dispatch from {request.remote_addr}, offering gateway at {gate_ip}:{_ds_mod.PORT}")
|
||||
response = classes_pb2.Dispatch(
|
||||
msg='OK',
|
||||
retcode=0,
|
||||
top_server_region_name='pp_sr',
|
||||
region_list=[
|
||||
classes_pb2.RegionInfo(
|
||||
name='ppSR',
|
||||
title='ppSR',
|
||||
dispatch_url=f'http://{gate_ip}:{_ds_mod.PORT}/query_gateway',
|
||||
env_type='9',
|
||||
display_name='ppSR'
|
||||
)
|
||||
]
|
||||
)
|
||||
return base64.b64encode(response.SerializeToString())
|
||||
|
||||
# Patch DispatchServer.run_server to bind 0.0.0.0
|
||||
if hasattr(_ds_mod, 'DispatchServer') and hasattr(_ds_mod.DispatchServer, 'run_server'):
|
||||
@classmethod
|
||||
def _patched_run_server(cls):
|
||||
try:
|
||||
import logging, flask
|
||||
logging.getLogger('werkzeug').disabled = True
|
||||
flask.cli.show_server_banner = lambda *args: None
|
||||
print(f"Dispatch server listening on 0.0.0.0:{_ds_mod.PORT}")
|
||||
print(f"Forwarding to 127.0.0.1:{_ds_mod.GAME_PORT}")
|
||||
_ds_mod.app.run(host="0.0.0.0", port=_ds_mod.PORT, threaded=False, use_reloader=False, debug=False)
|
||||
except Exception as ex:
|
||||
print(f"[DispatchServer Error]: {ex}")
|
||||
_ds_mod.DispatchServer.run_server = _patched_run_server
|
||||
|
||||
_app = _ds_mod.app
|
||||
_app.view_functions['query_gateway'] = _patched_query_gateway
|
||||
_ds_mod.query_gateway = _patched_query_gateway
|
||||
|
||||
_app.view_functions['query_dispatch'] = _patched_query_dispatch
|
||||
_ds_mod.query_dispatch = _patched_query_dispatch
|
||||
|
||||
@_app.before_request
|
||||
def _log_http_req():
|
||||
try:
|
||||
from flask import request as _req
|
||||
if any(k in _req.path for k in ['log', 'Upload', 'report']):
|
||||
body_preview = _req.get_data()[:300].decode('utf-8', errors='ignore')
|
||||
print(f"[HTTP Telemetry] {_req.method} {_req.path}: {body_preview}")
|
||||
else:
|
||||
print(f"[HTTP] {_req.method} {_req.path}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@_app.after_request
|
||||
def _log_http_rsp(resp):
|
||||
try:
|
||||
from flask import request as _req
|
||||
print(f"[HTTP] {_req.method} {_req.path} -> {resp.status_code}")
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
resp.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS, HEAD'
|
||||
resp.headers['Access-Control-Allow-Headers'] = '*'
|
||||
except Exception:
|
||||
pass
|
||||
return resp
|
||||
|
||||
def _handle_login_rsp(*args, **kwargs):
|
||||
try:
|
||||
return _ds_mod.login_by_password(), 200, {'Content-Type': 'application/json'}
|
||||
except Exception as e:
|
||||
print(f"[Login Error]: {e}")
|
||||
return '{"retcode":0,"message":"OK","data":{}}', 200, {'Content-Type': 'application/json'}
|
||||
|
||||
def _handle_load_config(*args, **kwargs):
|
||||
return '{"retcode":0,"message":"OK","data":{"client_custom_config":"","client_saved_config":""}}', 200, {'Content-Type': 'application/json'}
|
||||
|
||||
for _route in [
|
||||
'/<region>/account/ma-passport/api/appLoginByPassword',
|
||||
'/account/ma-passport/api/appLoginByPassword',
|
||||
'/<region>/account/ma-cn-passport/app/loginByPassword',
|
||||
'/account/ma-cn-passport/app/loginByPassword',
|
||||
'/<region>/account/ma-passport/token/verify',
|
||||
'/account/ma-passport/token/verify',
|
||||
'/<region>/account/auth/api/login',
|
||||
'/account/auth/api/login'
|
||||
]:
|
||||
try:
|
||||
_app.add_url_rule(_route, endpoint=f'flex_{_route}', view_func=_handle_login_rsp, methods=['OPTIONS', 'POST', 'GET'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for _cfg_route in [
|
||||
'/<region>/mdk/shield/api/loadConfig',
|
||||
'/mdk/shield/api/loadConfig',
|
||||
'/hkrpg_<region>/mdk/shield/api/loadConfig',
|
||||
'/hkrpg_global/mdk/shield/api/loadConfig'
|
||||
]:
|
||||
try:
|
||||
_app.add_url_rule(_cfg_route, endpoint=f'cfg_{_cfg_route}', view_func=_handle_load_config, methods=['OPTIONS', 'POST', 'GET'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@_app.errorhandler(405)
|
||||
def _handle_405(e):
|
||||
return '{"retcode":0,"message":"OK","data":{}}', 200, {'Content-Type': 'application/json'}
|
||||
|
||||
@_app.errorhandler(404)
|
||||
def _handle_404(e):
|
||||
try:
|
||||
from flask import request as _req
|
||||
print(f"[HTTP 404 Fallback] {_req.method} {_req.path}")
|
||||
if any(k in _req.path for k in ['login', 'passport', 'token', 'auth', 'shield']):
|
||||
return _ds_mod.login_by_password(), 200, {'Content-Type': 'application/json'}
|
||||
if 'combo' in _req.path:
|
||||
return _ds_mod.login_v2(), 200, {'Content-Type': 'application/json'}
|
||||
if 'risky' in _req.path:
|
||||
return _ds_mod.risky_check(), 200, {'Content-Type': 'application/json'}
|
||||
except Exception:
|
||||
pass
|
||||
return '{"retcode":0,"message":"OK","data":{}}', 200, {'Content-Type': 'application/json'}
|
||||
|
||||
print("[Patch] dispatchserver patched with reachable Gateserver IP, 4.6 protocol, and flexible routes")
|
||||
except Exception as e:
|
||||
print(f"[Patch Warning] Dispatch server patch: {e}")
|
||||
|
||||
# --- GameServer address & protected KCP loop patch ---
|
||||
try:
|
||||
import game.gameserver as _gs_mod
|
||||
if hasattr(_gs_mod, 'GameServer'):
|
||||
_gs_mod.GameServer.address = "0.0.0.0"
|
||||
|
||||
@classmethod
|
||||
def _patched_read_loop(cls):
|
||||
print(f"[GameServer] UDP KCP Listener active on {cls.address}:{cls.port}")
|
||||
while cls.running:
|
||||
try:
|
||||
message, address = cls.server_socket.recvfrom(_gs_mod.IKCP_MTU_DEF)
|
||||
if not message:
|
||||
continue
|
||||
|
||||
if len(message) == 20:
|
||||
print(f"[GameServer] Received Handshake ({len(message)} bytes) from {address}")
|
||||
cls.handle_handshake(address, message)
|
||||
continue
|
||||
|
||||
if not hasattr(cls, 'kcp') or cls.kcp is None:
|
||||
print(f"[GameServer Warning] Received {len(message)} bytes from {address} before KCP established, ignoring.")
|
||||
continue
|
||||
|
||||
cls.kcp.input(message)
|
||||
cls.kcp.flush()
|
||||
cls.kcp.update(int(time.time() * 1000))
|
||||
|
||||
packet = cls.kcp.recv()
|
||||
while len(packet) > 0 and cls.running:
|
||||
rsp = _router_mod.handle(packet)
|
||||
if len(rsp) != 0:
|
||||
cls.send(rsp)
|
||||
packet = cls.kcp.recv()
|
||||
|
||||
except Exception as ex:
|
||||
if cls.running:
|
||||
print(f"[GameServer Exception in read_loop]: {ex}")
|
||||
time.sleep(0.01)
|
||||
|
||||
_gs_mod.GameServer.read_loop = _patched_read_loop
|
||||
|
||||
@classmethod
|
||||
def _patched_handle_handshake(cls, address, data):
|
||||
try:
|
||||
from game.handshakeutils import HandShake, HandshakeType
|
||||
handshake = HandShake.parse(data)
|
||||
print(f"[GameServer] Parsed handshake type={handshake} from {address}")
|
||||
if handshake == HandshakeType.CONNECT:
|
||||
cls.establish_kcp()
|
||||
rsp_handshake = HandShake.build_rsp_handshake(cls.conv_id)
|
||||
cls.client_socket = address
|
||||
cls.send_udp(rsp_handshake)
|
||||
print(f"[GameServer] Handshake accepted! Sent response to {address}, conv_id={cls.conv_id}")
|
||||
except Exception as ex:
|
||||
print(f"[GameServer Handshake Error]: {ex}")
|
||||
|
||||
_gs_mod.GameServer.handle_handshake = _patched_handle_handshake
|
||||
|
||||
@classmethod
|
||||
def _patched_send_udp(cls, data):
|
||||
if cls.client_socket is not None:
|
||||
try:
|
||||
cls.server_socket.sendto(data, cls.client_socket)
|
||||
except Exception as ex:
|
||||
print(f"[GameServer send_udp Error]: {ex}")
|
||||
|
||||
_gs_mod.GameServer.send_udp = _patched_send_udp
|
||||
|
||||
_orig_gs_start = _gs_mod.GameServer.start
|
||||
@classmethod
|
||||
def _safe_gs_start(cls):
|
||||
cls.address = "0.0.0.0"
|
||||
try:
|
||||
sock = getattr(cls, 'server_socket', None)
|
||||
if sock is None or getattr(sock, 'fileno', lambda: -1)() == -1:
|
||||
cls.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
cls.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
_orig_gs_start()
|
||||
print(f"[GameServer] Bound UDP socket on {cls.address}:{cls.port}")
|
||||
except Exception as ex:
|
||||
print(f"[GameServer Warning] start error: {ex}")
|
||||
|
||||
_gs_mod.GameServer.start = _safe_gs_start
|
||||
print("[Patch] GameServer address patched to 0.0.0.0 with protected read_loop and handshake logging")
|
||||
except Exception as e:
|
||||
print(f"[Patch Warning] GameServer patch: {e}")
|
||||
|
||||
ppsr_py = os.path.join(ppsr_dir, "ppsr.py")
|
||||
if os.path.exists(ppsr_py):
|
||||
print(f"Loading source script target: {ppsr_py}...")
|
||||
with open(ppsr_py, "r", encoding="utf-8") as f:
|
||||
code_str = f.read()
|
||||
code_obj = compile(code_str, ppsr_py, "exec")
|
||||
target_file = ppsr_py
|
||||
else:
|
||||
print(f"Loading bytecode target: {ppsr_path}...")
|
||||
with open(ppsr_path, "rb") as f:
|
||||
code_bytes = f.read()
|
||||
try:
|
||||
import marshal
|
||||
code_obj = marshal.loads(code_bytes[16:])
|
||||
except Exception:
|
||||
code_obj = compile(code_bytes, ppsr_path, "exec")
|
||||
target_file = ppsr_path
|
||||
|
||||
main_globals = {
|
||||
"__name__": "__main__",
|
||||
"__file__": ppsr_path,
|
||||
"__file__": target_file,
|
||||
"__doc__": None,
|
||||
"sys": sys,
|
||||
"os": os,
|
||||
@@ -498,7 +976,7 @@ def run_server(ppsr_path, activity_context=None):
|
||||
|
||||
try:
|
||||
exec(code_obj, main_globals)
|
||||
except KeyboardInterrupt:
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
print("[System]: Server loop interrupted cleanly.")
|
||||
except Exception as e:
|
||||
print(f"[System]: Server terminated: {e}")
|
||||
Reference in new issue
Block a user