This repository records a careful reverse-engineering pass over the ARM64
libqplay.so shipped with Graal Online Classic 1.8, together with a small
set of local-only protocol tools. The goal is to make the work reproducible
for people who have a lawful copy of the client and are trying to understand
why an old installation no longer starts.
The notes are written as a lab record. They separate facts observed in the binary, facts reproduced in an emulator, and hypotheses that still need a server-side test. That distinction matters here because a successful TCP handshake is not the same thing as a successful game login.
The symbol pass is complete for every name retained in the ELF export. The verified ARM64 IDA pass also contains the reviewed callback, script-table, static-library, and exact embedded-library source aliases. The counts are:
| Kind | Count |
|---|---|
| Retained ELF symbols translated | 8,601 |
| Additional reviewed function aliases | 1,551 |
| IDA functions in the saved copy | 11,296 |
| Residual functions with descriptive IDA labels | 124 |
The retained ELF count includes 4,714 implementation functions, 3,183 PLT
thunks, 199 jump thunks, and 505 data symbols. The 124 residual entries are
code that IDA discovered without a preserved source symbol. They now have
stable address-based analyst labels, with their original sub_ names and
roles preserved in artifacts/ida_descriptive_residual_labels_20260902.json.
The exact source role pass covers 141 FreeType 2.3.6 functions, 153 IJG
libjpeg 6b functions, one zlib 1.2.5 function, and one static giflib GIF
decoder helper. The compact verification record is
artifacts/ida_translation_verification_20260902.json.
The active-IDB scope check found zero default names after the residual label
pass, zero residual functions in the Android bridge range, zero among the
1,779 unique script callback addresses, and zero direct calls from a residual
function into the selected socket, file, process, or update imports. The
broader application-core range contains 23 short static-state or cleanup
wrappers, which are documented with descriptive labels without inventing
source names. The check is preserved in
artifacts/ida_active_translation_scope_check_20260902.json.
The zero-failure counts above come from the completed verification of the
private v14 IDA copy. The latest private v16 copy adds connector comments but
has not been independently closed and reopened because the IDA bridge is not
currently available. Its persistence status is recorded in
artifacts/ida_active_copy_status_20260904.json.
The old connector has a concrete compatibility problem. Its embedded
GraalWeb certificate expired on 2023-07-29, so the original HTTPS path cannot
be trusted by a current clock. The archived connector fixture is structurally
valid and passes the native RSA check when verified with the recovered wolfSSL
raw-digest format. An earlier generic ASN.1 DigestInfo check reported a
mismatch because it used the wrong signature format. Certificate freshness
and package verification are separate from the game-server protocol, and any
diagnostic changes remain private to the local test build.
The connector fallback audit found a second, more specific behavior. When the
native CyaSSL read or write path records a TLS error, the connector skips its
remaining HTTPS retry in modes 1 and 2 and jumps to the cleartext mode 3
conf.gs endpoint. A private two-port emulator control confirmed the runtime
transition, including the failed TLS leg and the subsequent plain
GET /conf.gs. The static and runtime records are
artifacts/connector_fallback_review_20260902.json and
artifacts/connector_fallback_runtime_control_20260902.json.
The connector also has a delayed TLS gate. It opens a nonblocking TCP socket,
polls status 4 with zero-timeout select, and starts CyaSSL only after
SO_ERROR moves the socket to status 5. A device trace with TCP but no TLS
ClientHello therefore needs the render and timer loop checked before it is
classified as a certificate failure. The static state record is
artifacts/connector_socket_state_review_20260902.json.
That control reused the archived /con.png package as the /conf.gs body and
did not reach the synthetic game listener. The current mode-3 response is
still unverified. A follow-up local control used the mode-specific
StartScript_Fail package role described by the supplied Moreno.kahn workbench.
With a matching test public key and native RSA verification left enabled, the
emulator accepted the 959-byte package and logged
MODE3_FAIL_SCRIPT_REACHED. This proves that the mode-3 package can be
unpacked and activated. It does not prove that the current service publishes
that package or that the failure script should start a game. The runtime record
is artifacts/connector_mode3_fail_script_runtime_control_20260902.json.
A bounded native parser control then tested malformed but correctly signed ZIP
envelopes. Bad signatures, missing EOCD records, invalid local offsets, and
oversized script metadata were rejected without a crash indicator. A package
missing only the final ZIP byte still executed, so the native reader has a
format-integrity gap that deserves stricter end-record and exact-read checks.
The compact evidence is in
artifacts/connector_package_negative_controls_20260902.json.
The symbolized handler-table investigation also produced an important
correction. The original setInDataHandlers instructions are correct for this
client revision. The earlier x86_64 xchg patch and the matching ARM64
operand swap were a false lead caused by reading the intermediate bytecode
array in the wrong order. The decoded runtime pairs are packet type first,
handler index second, and the unmodified table accepts the normal local
sequence.
The corrected two-connection replay now renders the level tile field, player
HUD, and status icons. The client accepts a server-warp, a player-properties
packet, the connecting-window completion packet, the map transition, three
encrypted level containers, and pics1.png. The file response is packet 102,
not packet 59. A direct packet-59 parser jump was retained only as a negative
control because it breaks the normal request sequence.
The game has not yet been verified against a live game server. The local test proves that the native client can reach a rendered world through a bounded loopback responder. Live endpoint availability, current package signing, and account authentication remain open.
The package-specific comparison material has been removed from this archive. Future cross-version work should use a separately verified input and keep its reports in an explicitly scoped branch.
The Android lifecycle review adds an important first diagnostic checkpoint.
The connector is downstream of the GL surface, window focus, runtime
permission completion, and the first render frame. A compatibility dialog or
permission state can therefore make the app look offline before any TLS code
has run. The native Java bridge and ARM64 JNI evidence are in
artifacts/original_android_lifecycle_review_20260830.json.
The incoming deep-link path has a separate mismatch. Android accepts
graalclassic:// and graalclassicplus://, while the native start parser only
normalizes graal:// and graal3://. The full URI is also delivered to the
script event onStartedWithURL. This is now documented as a compatibility lead
and a conditional destination-control concern in
artifacts/original_intent_launch_review_20260830.json.
The native HTTP redirect pass found another destination-control boundary.
Responses with status 300 through 303, 305, or 307 can replace the request's
host, port, path, and scheme from a Location value. The retry limit is ten,
but the old code does not compare the new destination with the original one
or prevent an HTTPS-to-HTTP downgrade. This is a confirmed static finding,
not a live-service result. The evidence is in
artifacts/original_http_redirect_review_20260830.json.
The script-to-Android URL pass found a separate outbound capability. The
openurl and openurl2 callbacks reach QPlayActivity.openURL; non-legacy
inputs become ACTION_VIEW intents without a visible scheme or host
allowlist, while canopenurl reports whether Android has a matching handler.
The server-mediated opengraalurl path normally sends a game message, but it
falls back to the same bridge when no active client exists. The static evidence
is in artifacts/original_external_url_review_20260830.json.
The HTTP framing pass confirms that the 8,192-byte native read size is only a
chunk size. Header lines and accumulated response data have no general cap,
and the parser does not decode Transfer-Encoding: chunked. Legacy
Content-Length and close-delimited responses work in the bounded local
replay, but a current server's framing still needs to be verified. The
evidence is in artifacts/original_http_framing_review_20260830.json.
The cache write pass found a second compatibility lead. The native writer uses
fwrite without checking the returned count, and the resource state is updated
after the write without an atomic temporary-file rename. A truncated external
cache can therefore look like a completed download on the next launch. URL
derived names are escaped into webfiles, but ordinary path handling still
uses lexical and filesystem heuristics. The focused evidence is in
artifacts/cache_filename_policy_review_20260902.json.
The Facebook bridge pass found that the Android script table exposes more than
login status. An activated script can read the current Facebook access token,
request additional read or publish permissions, issue authenticated Graph GET,
POST, or DELETE requests, and use the graph2 path to upload eligible game
resources. The SDK builds HTTPS Graph URLs by default, but transport security
does not remove the risk of handing a bearer token to script code. The focused
IDA and DEX review is in
artifacts/original_facebook_bridge_review_20260830.json.
The Google Play pass found a separate legacy billing bridge. The Android
script table can start an in-app purchase with caller-supplied SKU and
developer-payload text. The store still controls availability and signs the
result, but the activity reports the immediate launch Boolean before the
asynchronous result is known. The later callback sends status, SKU, original
purchase JSON, and signature back into native script events even when the
purchase signature was rejected. The focused review is in
artifacts/original_billing_bridge_review_20260830.json.
The legacy partner pass found that the remaining TapJoy, Distimo, Fabzat, and
TrialPay entries are compatibility remnants in this APK. Their Java methods
are no-ops or return false, while five Fabzat callbacks are native nullsubs.
The native TapJoy setters still retain script-provided strings in process
memory, but the stock Java bridge never sends them to a working SDK. The
focused evidence is in
artifacts/original_partner_bridge_review_20260830.json.
The Android device and media pass found two useful compatibility details. The
script table can read the Android release, manufacturer, model, and display
metrics, while the native video path is only partly wired: the activity's
video open and stop methods are no-ops and the rectangle setter has no matching
static method in the reviewed Natives class. Keyboard closure is still a
real asynchronous input path. The focused evidence is in
artifacts/original_android_device_media_review_20260830.json.
The offline ELF loader inventory adds one more compatibility lead. All four
packaged native variants declare libstdc++.so, but the APK does not package a
library with that name. The ARM64 library uses 0x10000 ELF LOAD alignment;
the other three use 0x1000. The earlier x86_64 replay loaded successfully, so
this does not identify the current failure by itself. An ARM64 device logcat is
still needed to distinguish a missing or incompatible runtime dependency from
the separate stale-certificate problem. The expanded records are in
artifacts/original_apk_security_audit_20260830.json.
The cross-ABI comparison confirms that all four native variants contain the
same connector and CyaSSL marker set and the same 12,820-byte embedded trust
text hash. Their five native dependencies and baseline ELF hardening flags are
also shared. This makes the stale connector material a build-wide compatibility
lead, while the different code layouts and ARM64 0x10000 alignment still
require a real ARM64 loader test. The compact metadata-only report is
artifacts/cross_abi_compatibility_review_20260902.json, generated by
tools/generate_cross_abi_compatibility_review.py.
The report also carries 34 shared address anchors for the startup, connector,
socket, protocol, JNI, and CyaSSL paths, without claiming that those addresses
transfer to another release.
The native init/fini review confirms that libqplay.so runs a fixed 20-entry
constructor array before QPlayMain and a 10-entry teardown array on unload.
The callbacks initialize resource lists, texture state, GUI defaults, client
state, and video globals. None of the callbacks directly reaches the selected
socket, resolver, file, or process boundaries. Their decompiler output is
preserved in artifacts/original_native_init_review_20260830.json.
The AArch64 import pass now inventories every direct BL and unconditional
B tail transfer from the original .text section to a PLT import. It found
167 undefined ELF symbols, 3,186 transfer sites, and 301 tail calls. The
TSocket property table exposes bind, connect, send, and sendudp; the
native socket helper contains socket, bind, listen, accept, TCP, and
UDP branches. The static constructor clears the bind and outbound-socket
allowlist strings before the script setters can replace them. This is a
conditional local-listener and datagram capability, not proof that the stock
connector starts a listener or contacts an endpoint. The compact inventory is
in artifacts/original_aarch64_import_callsite_inventory_20260830.json,
generated by tools/audit_aarch64_import_calls.py.
The follow-up socket-policy pass resolved the matching rules and found a real
range bug. Exact host and port entries are checked before native socket
creation, but slash-separated ranges compare the comma-separated entry index
instead of the requested port in both outbound and bind paths. The native bind
helper also zeros the IPv4 address, so a permitted listener is wildcard-bound
rather than statically loopback-only. The same pass records the 100 MiB
selected-download limit and ten-request HTTP pool default. Its evidence is in
artifacts/original_socket_policy_review_20260902.json. The rerunnable IDA
exporter is tools/ida_export_original_socket_policy_review.py.
The remaining Android callback pass closes the native side of the Java bridge.
It records the touch, key, text, surface, lifecycle, script-registration,
purchase, legacy-provider, video, and generic event callbacks. In particular,
onAppPause can set the native close flag before a client exists or while the
client is still in its early loading states, and the next QPlayLoop exits the
process. That is a startup compatibility lead that can look like an offline
failure. The native export is in
artifacts/original_android_callback_review_20260830.json.
The companion DEX inventory finds 18 public static native methods in the
Natives class, 14 with 21 direct invoke-* callsites. Four exported methods
have no direct DEX caller in this APK: onAddScriptFunction, onRegisterEvent,
onVideoFinished, and onVideoLoaded. That does not rule out reflection or
native-to-Java callbacks, but it keeps their active reachability unproven.
The compact record is
artifacts/original_dex_native_surface_review_20260830.json, generated by
tools/audit_dex_native_surface.py.
The follow-up method-level DEX pass resolves the actual invoke-* references
behind that inventory. It parsed all 11,952 methods with code from the 1,889
class definitions without a bounds error, and records 21 direct calls into 14
of the 18 native methods. It also separates the four application-owned
WebView callers from the bundled Bolts bridge and identifies the renderer's
intent reads before QPlayMain. The compact record is
artifacts/original_dex_method_surface_review_20260904.json, generated by
tools/audit_dex_method_surface.py.
docs/RESEARCH_NOTES.mdis the chronological investigation record.docs/PROTOCOL.mddescribes the connector and NewGraal wire formats.docs/LEVEL_CONTAINER.mddescribes the encrypted.codelevel container.docs/SYMBOLS.mdexplains the symbol export and naming policy.docs/RUNTIME_STATUS.mdlists verified milestones and open blockers.docs/TESTING.mddescribes local-only reproduction without contacting a live game service.docs/SECURITY.mdrecords the original APK and ARM64 native trust-boundary review.docs/UPDATE_PACKAGES.mdexplains theGRPKG001manifest parser, ordinary and large-file transfer state, and the bounded completion-path crash review.docs/DEPENDENCY_PROVENANCE.mdrecords the bundled compression, font, and image-library versions and their input paths.artifacts/original_dex_webview_review_20260830.jsonrecords the local smali review that separates the game WebView, Bolts bridge, and Facebook SSL-error path.artifacts/connector_http_flow_review_20260830.jsonpreserves the compact IDA export for the original connector request lifecycle.artifacts/connector_fallback_review_20260902.jsonrecords the connector retry state machine and the TLS-error jump to its cleartext fallback.artifacts/connector_fallback_runtime_control_20260902.jsonrecords the private two-port runtime observation and its response-format limitation.artifacts/connector_mode3_fail_script_runtime_control_20260902.jsonrecords the matching mode-3 package andStartScript_Failexecution control.artifacts/connector_package_negative_controls_20260902.jsonrecords the targeted malformed signed-package controls and the one-byte EOF leniency.artifacts/cross_abi_compatibility_review_20260902.jsoncompares the four native ABI variants without copying binaries into the repository.artifacts/game_connection_flow_review_20260830.jsonpreserves the compact IDA export for the connector-to-game socket lifecycle.artifacts/original_libc_callsite_review_20260830.jsonrecords the direct libc call sites and focused buffer review for the original ARM64 library.artifacts/original_level_parser_review_20260830.jsonrecords the encrypted level, board, and line-oriented entity parser review.artifacts/original_download_cache_flow_review_20260830.jsonrecords the server-file response, resource resolution, and cache save flow.artifacts/original_update_package_path_review_20260830.jsonrecords the update-package manifest parser, package path policy, cache path mapping, and local file writers.artifacts/original_update_integrity_review_20260830.jsonrecords the request-side CRC fields, download scheduler, response accumulation, and package completion boundary.artifacts/original_script_package_review_20260830.jsonrecords the signed connector script package parser and activation path. Embedded crypto literals are redacted from this public report.artifacts/original_game_login_review_20260830.jsonrecords the NewGraal key setup, framing, sequence, and packet-54 login transition. Fixed key-like literals are redacted from this public report.artifacts/original_credential_storage_review_20260830.jsonrecords the native option store, reversible value transform, and script-facing account, password, and cookie boundaries.artifacts/original_script_capability_review_20260830.jsonrecords the embedded script callbacks that reach local files, uploads, HTTP requests, protocol controls, and dynamic class loading.artifacts/original_image_parser_review_20260830.jsonrecords the downloaded image-resource path and the native PNG, GIF, JPEG, BMP, and TGA decoder boundaries. It is a static review and contains no fuzzing results.artifacts/original_dependency_provenance_20260830.jsonrecords the exact bundled zlib, bzip2, and FreeType version evidence, compression callsites, font paths, and conservative dependency findings.artifacts/ida_freetype_source_matches_20260901.jsonrecords 141 exact FreeType 2.3.6 source matches with address, size, xref, and source-line evidence.artifacts/ida_libjpeg_source_matches_20260902.jsonrecords 153 exact IJG libjpeg 6b source matches, including the corrected marker-reader roles.artifacts/ida_zlib_source_matches_20260902.jsonrecords the exactinflate_fastmatch at0x28a2f4.artifacts/ida_giflib_source_matches_20260902.jsonrecords the staticDGifDecompressLinerole at0x2acb20while leaving the exact giflib release open.artifacts/gif_decoder_security_review_20260902.jsonrecords the GIF LZW bounds-check comparison, the conditional review of three upstream giflib CVEs, and the independentDGifSlurpdimension-arithmetic and extension- accumulation findings plus the unboundedSavedImagesframe-array review. The same review records a conditional bitmap-copy overflow candidate.artifacts/png_decoder_security_review_20260902.jsonrecords the PNG/MNG raw-scanline and decoded-output size arithmetic, including a conditional 32-bit dimension-wrap overflow candidate and its concrete arithmetic witness.artifacts/bmp_tga_decoder_security_review_20260902.jsonrecords the BMP palette stack boundary, BMP row-size wrap, and TGA decoded-size wrap. These are static conditional findings with no malformed-image runtime result.artifacts/jpeg_decoder_security_review_20260902.jsonrecords the JPEG decoder-dimension path into the shared bitmap allocator and scanline copy, including a conditional 32-bit RGB size-wrap candidate.artifacts/compression_buffer_security_review_20260902.jsonrecords the shared zlib and bzip2 buffer growth loop, the signed-size stall witness, and the process-wide high-water behavior outside the nominal 4 MiB decompression retry policy.artifacts/zip_resource_security_review_20260902.jsonrecords the minizip resource and script-extraction paths, aggregate decoded-size gap, short-read handling, and conditional inflate-initialization cleanup leak.artifacts/cache_filename_policy_review_20260902.jsonrecords URL-derived filename escaping, ordinary cache path classification, unchecked file-write results, URLCACHE persistence, and the resulting startup-integrity lead.artifacts/synthetic_basepackage_crash_20260902.jsonrecords a local negative control in which a metadata-only base package reached a native null dereference. It contains hashes and stack metadata, not raw package data.artifacts/update_package_transfer_review_20260902.jsonrecords the packet-68, packet-84, packet-102, and packet-69 controls, the ARM64 handler addresses, and the limits of the x86_64 diagnostic result.artifacts/static_library_role_audit_20260901.jsonrecords 30 high- confidence bundled-library role aliases, including the final bzip2 stream callbacks.artifacts/original_residual_semantic_review_20260830.jsonrecords the historical address-level behavior of the two selected FreeType diagnostic helpers. Both helpers are now source-labeled in the current IDA copy.artifacts/original_android_lifecycle_review_20260830.jsonrecords the Activity, GLThread, renderer, and ARM64 JNI startup and pause boundaries.artifacts/original_intent_launch_review_20260830.jsonrecords the Android custom-scheme entrypoint, native URI parser, and script-visible start fields.artifacts/original_http_redirect_review_20260830.jsonrecords native HTTP redirect handling, destination replacement, and transport downgrade behavior.artifacts/original_external_url_review_20260830.jsonrecords script URL callbacks, Android intent construction, and installed-handler probing.artifacts/original_http_framing_review_20260830.jsonrecords response accumulation, header limits, transfer coding, and body completion behavior.artifacts/original_facebook_bridge_review_20260830.jsonrecords the Facebook session, permission, Graph request, and game-resource upload callbacks. It contains no real token or login result.artifacts/original_billing_bridge_review_20260830.jsonrecords the legacy Google Play billing script, JNI, purchase verification, and callback path. It contains no purchase data, signature, or embedded key material.artifacts/original_partner_bridge_review_20260830.jsonrecords the TapJoy, Distimo, Fabzat, TrialPay, Amazon, and Mobiroo compatibility paths, including their Java no-op behavior.artifacts/original_android_device_media_review_20260830.jsonrecords the Android build-info, display-metric, virtual-keyboard, and legacy video bridges. It contains no device-collected values or media data.artifacts/original_native_init_review_20260830.jsonrecords the fixed ELF init/fini callback arrays and their static-state behavior.artifacts/original_aarch64_import_callsite_inventory_20260830.jsonrecords direct AArch64 import transfers, including tail calls, and their containing functions.artifacts/original_android_callback_review_20260830.jsonrecords the remaining native Android JNI callback bodies and their behavior summaries.artifacts/original_dex_native_surface_review_20260830.jsonrecords the Natives class access flags and direct Java bytecode callsites.artifacts/ida_translation_verification_20260902.jsonrecords the final packed IDA copy, pass counts, hashes, and zero-failure verification result.artifacts/ida_descriptive_residual_labels_20260902.jsonrecords the 124 evidence-backed labels applied to functions that had no preserved ELF name.artifacts/ida_final_residual_audit_20260902.jsonrecords the current compact address-level residual audit.artifacts/ida_active_translation_scope_check_20260902.jsonrecords the active-IDB check that separates remaining static wrappers from app-boundary functions.symbols/libqplay.symbols.csvis the searchable symbol table.symbols/libqplay.symbols.jsonis the machine-readable equivalent.symbols/libqplay.symbols.summary.jsonrecords the translation counts.symbols/libqplay.function_inventory.jsonis the current 11,296-row IDA function inventory with descriptive residual labels applied.symbols/libqplay.function_inventory.csvis the same inventory in a searchable tabular form, and its summary records zero defaultsub_names.tools/contains IDAPython, parsing, replay, and diagnostic patch helpers.artifacts/contains small metadata exports. APKs, certificates, private keys, captured credentials, and game assets are intentionally not included.
The primary input was the ARM64 library from the original Graal Online Classic 1.8 APK. The x86_64 library from the same package was used for the initial repeatable emulator experiments because the available Android emulator is x86_64. Later private diagnostic APKs exercised the ARM64 library through that emulator's translation layer. Those translated-ARM64 replays are useful local evidence, but they are not physical ARM64 device validation.
Two helper repositories were also checked out locally during the work:
MorenoLand/GScript.Go-HexaParser, used as a reference for GS2 bytecode tooling.MorenoLand/Moreno.kahn, used to validate the archived connector package and itscon.pngcontainer.
Their source is not vendored here. The exact commits and their role in the local validation are recorded in the research notes.
The tools are intended for an owned or otherwise authorized copy of the client, and the runtime tests are designed to stay on loopback. The patch helpers are diagnostic artifacts. They bypass stale package verification or redirect a test endpoint, so they should not be installed as a general client repair without first replacing the endpoint and trust material with values that are independently verified.
Do not put account passwords, private keys, live server responses, or copied game assets into commits. Hashes and structural metadata are enough to make the analysis auditable without publishing secrets or a full game data set.
The highest-value remaining work is live-service and ARM64 validation. The local path is already complete through world rendering, so the next checks are to verify the current connector trust and package-signing chain, repeat the same packet sequence on a real ARM64 device, and compare the live server's resource and login responses with the captured local trace. Those tests should only use an endpoint and account that the operator is authorized to test.