mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 10:00:27 +02:00
Added Retro Rewind Quest Support
This commit is contained in:
1 parent
0bf2675db1
commit
58c0890983
18 files changed
+378
-168
No files matched your search
@@ -51,7 +51,7 @@ add_subdirectory("${MKW_REPO_ROOT}/runtime" wiicompiled-runtime)
|
||||
|
||||
# App-owned JNI glue lives in each product library SDLActivity loads, and in the game kit
|
||||
# probe (runtime/cmake/PublicProducts.cmake) whose objects the player's game is linked with.
|
||||
foreach(product IN ITEMS WiiCompiled RetroRewind mkw_quest_kit_probe)
|
||||
foreach(product IN ITEMS WiiCompiled RetroRewind mkw_quest_kit_probe mkw_quest_kit_probe_retro)
|
||||
if(TARGET ${product})
|
||||
target_sources(${product} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/quest_surface_jni.cpp")
|
||||
target_include_directories(${product} PRIVATE "${MKW_REPO_ROOT}/aurora-main/include")
|
||||
|
||||
@@ -17,6 +17,8 @@ object GameStorage {
|
||||
// Must match kApplicationDirectoryName in runtime/include/runtime_config.h.
|
||||
const val APP_DIRECTORY = "WiiCompiledOpenXRVR"
|
||||
const val DISC_DIRECTORY = "DATA"
|
||||
/** The Retro Rewind pack the modded product reads (`[paths] retro_rewind_root`). */
|
||||
const val MOD_DIRECTORY = "RetroRewind6"
|
||||
|
||||
enum class DiscStatus { Missing, Incomplete, Ready }
|
||||
|
||||
@@ -26,6 +28,12 @@ object GameStorage {
|
||||
|
||||
fun discDirectory(context: Context): File = File(gameRoot(context), DISC_DIRECTORY)
|
||||
|
||||
fun modDirectory(context: Context): File = File(gameRoot(context), MOD_DIRECTORY)
|
||||
|
||||
/** Whether this app needs the Retro Rewind pack and has it. Always true for the base game. */
|
||||
fun modContentReady(context: Context): Boolean =
|
||||
BuildConfig.PROFILE != "retro_rewind" || File(modDirectory(context), "Binaries/Code.pul").isFile
|
||||
|
||||
fun configFile(context: Context): File = File(gameRoot(context), "Config.toml")
|
||||
|
||||
fun logsDirectory(context: Context): File = File(gameRoot(context), "Logs")
|
||||
@@ -60,22 +68,28 @@ object GameStorage {
|
||||
importDirectory(context).mkdirs()
|
||||
val config = configFile(context)
|
||||
if (!config.isFile) {
|
||||
val disc = discDirectory(context).absolutePath
|
||||
config.writeText(
|
||||
"""
|
||||
# WiiCompiled Quest configuration. Edit with the launcher, the in-game panel or adb pull/push.
|
||||
[paths]
|
||||
dvd_root = "$disc"
|
||||
|
||||
[video]
|
||||
widescreen = true
|
||||
resolution_multiplier = 1.0
|
||||
|
||||
[vr]
|
||||
enabled = true
|
||||
render_scale = 1.0
|
||||
""".trimIndent() + "\n",
|
||||
// Written line by line: trimIndent runs after interpolation, so an interpolated line
|
||||
// would take the indent off every other one.
|
||||
val lines = mutableListOf(
|
||||
"# WiiCompiled Quest configuration. Edit with the launcher, the in-game panel or adb pull/push.",
|
||||
"[paths]",
|
||||
"dvd_root = \"${discDirectory(context).absolutePath}\"",
|
||||
)
|
||||
// The modded product reads its pack from retro_rewind_root, which sits next to DATA here.
|
||||
if (BuildConfig.PROFILE == "retro_rewind") {
|
||||
lines += "retro_rewind_root = \"${modDirectory(context).absolutePath}\""
|
||||
}
|
||||
lines += listOf(
|
||||
"",
|
||||
"[video]",
|
||||
"widescreen = true",
|
||||
"resolution_multiplier = 1.0",
|
||||
"",
|
||||
"[vr]",
|
||||
"enabled = true",
|
||||
"render_scale = 1.0",
|
||||
)
|
||||
config.writeText(lines.joinToString("\n", postfix = "\n"))
|
||||
}
|
||||
return gameRoot
|
||||
}
|
||||
|
||||
@@ -9,10 +9,6 @@ import java.security.MessageDigest
|
||||
*/
|
||||
object BuildRecipe {
|
||||
|
||||
/** The translated source lists emit-build-shards writes into shards.cmake, in link order. */
|
||||
val TRANSLATED_LISTS = listOf("MKW_BASE_COMMON_SHARDS", "MKW_BASE_PORTABLE_SENSITIVE_SHARDS")
|
||||
const val REGISTRATION_LIST = "MKW_BASE_REGISTRATION_SOURCES"
|
||||
|
||||
/**
|
||||
* One argument of a response file for clang or lld on Android, which read them with GNU
|
||||
* quoting: a backslash escapes the next character.
|
||||
|
||||
@@ -53,6 +53,7 @@ object GameBuild {
|
||||
}
|
||||
|
||||
private const val TAG = "WiiCompiledLauncher"
|
||||
private const val KIT_SCHEMA = 2
|
||||
private const val TOOLCHAIN_ASSETS = "quest_toolchain"
|
||||
private const val KIT_ASSETS = "game_kit"
|
||||
private const val MARKER = ".complete"
|
||||
@@ -66,6 +67,9 @@ object GameBuild {
|
||||
|
||||
/** Null on success, otherwise the message to show. */
|
||||
fun run(context: Context, reporter: Reporter, cancelled: () -> Boolean, finishing: () -> Unit): String? {
|
||||
if (!BuildConfig.ON_DEVICE_BUILD) {
|
||||
return "This app cannot build its game on the headset. Build it on a computer and use Import from computer."
|
||||
}
|
||||
val disc = GameStorage.discDirectory(context)
|
||||
if (GameStorage.discStatus(context) != GameStorage.DiscStatus.Ready) {
|
||||
return "The game files (DATA) are needed to build the game. Select your disc image first."
|
||||
@@ -120,6 +124,9 @@ object GameBuild {
|
||||
val kitFingerprint = GameLibrary.kitFingerprint(context) ?: return "This app carries no game kit."
|
||||
unpackKit(kitFingerprint)
|
||||
val recipe = JSONObject(File(kit, "kit.json").readText())
|
||||
if (recipe.optInt("schema") != KIT_SCHEMA) {
|
||||
return "This app's game kit is version ${recipe.optInt("schema")}, which this builder does not know."
|
||||
}
|
||||
report(40, Step.Prepare)
|
||||
downloadNdk(JSONObject(context.assets.open("$TOOLCHAIN_ASSETS/${toolchainManifest.getString("ndk")}").reader().use { it.readText() }))
|
||||
|
||||
@@ -336,21 +343,42 @@ object GameBuild {
|
||||
stamp.writeText(identity)
|
||||
}
|
||||
|
||||
val blob = File(generated, "data_sections_init_blobs.S")
|
||||
val elfBlob = File(work, "data_sections_init_blobs_android.S")
|
||||
val blobText = blob.readText()
|
||||
val elfText = BuildRecipe.elfBlobAssembly(blobText)
|
||||
if (!elfBlob.isFile || elfBlob.readText() != elfText) elfBlob.writeText(elfText)
|
||||
// Blob assembly generated on Windows is rewritten for ELF, as PublicProducts.cmake does.
|
||||
fun elfAssembly(source: File): File {
|
||||
val rewritten = File(work, source.nameWithoutExtension + "_android.S")
|
||||
val text = BuildRecipe.elfBlobAssembly(source.readText())
|
||||
if (!rewritten.isFile || rewritten.readText() != text) rewritten.writeText(text)
|
||||
return rewritten
|
||||
}
|
||||
|
||||
// Which sources fill each link slot is the recipe's to say, so this builds either flavour.
|
||||
data class Job(val kind: String, val source: File, val slot: String)
|
||||
val jobs = ArrayList<Job>()
|
||||
jobs += Job("runtime", File(generated, "data_sections_init.cpp"), "runtime")
|
||||
jobs += Job("runtime", File(generated, "guest_symbol_table.cpp"), "runtime")
|
||||
jobs += Job("asm", elfBlob, "runtime")
|
||||
BuildRecipe.sourceList(shards, BuildRecipe.REGISTRATION_LIST).forEach { jobs += Job("product", File(it), "product") }
|
||||
val translated = BuildRecipe.TRANSLATED_LISTS.flatMap { BuildRecipe.sourceList(shards, it) }
|
||||
if (translated.isEmpty()) throw IOException("The translation produced no game code.")
|
||||
translated.forEach { jobs += Job("translated", File(it), "translated") }
|
||||
val sources = recipe.getJSONObject("sources")
|
||||
for (slot in sources.keys()) {
|
||||
val entries = sources.getJSONArray(slot)
|
||||
val slotSources = ArrayList<File>()
|
||||
for (i in 0 until entries.length()) {
|
||||
val entry = entries.getString(i)
|
||||
when {
|
||||
entry.startsWith("@") -> BuildRecipe.sourceList(shards, entry.substring(1)).forEach { slotSources += File(it) }
|
||||
else -> slotSources += File(BuildRecipe.expand(entry, mapOf("workspace" to workspace.absolutePath)))
|
||||
}
|
||||
}
|
||||
if (slotSources.isEmpty()) {
|
||||
throw IOException("The translation has no $slot sources for a ${recipe.getString("product")} game.")
|
||||
}
|
||||
// Assembly is assembled; a mod shard compiles with the same flags as a translated one.
|
||||
slotSources.forEach { source ->
|
||||
val assembly = source.name.endsWith(".S")
|
||||
val kind = when {
|
||||
assembly -> "asm"
|
||||
slot == "mod" -> "translated"
|
||||
else -> slot
|
||||
}
|
||||
jobs += Job(kind, if (assembly) elfAssembly(source) else source, slot)
|
||||
}
|
||||
}
|
||||
if (jobs.map { BuildRecipe.objectName(it.source.path) }.toSet().size != jobs.size) {
|
||||
throw IOException("Two generated sources share an object name.")
|
||||
}
|
||||
@@ -361,7 +389,7 @@ object GameBuild {
|
||||
"workspace" to workspace.absolutePath,
|
||||
)
|
||||
val compileFlags = recipe.getJSONObject("compile")
|
||||
for (kind in listOf("runtime", "asm", "product", "translated")) {
|
||||
for (kind in jobs.map { it.kind }.distinct()) {
|
||||
val flags = compileFlags.getJSONArray(kind).let { array -> (0 until array.length()).map { BuildRecipe.expand(array.getString(it), values) } }
|
||||
File(work, "$kind.rsp").writeText(BuildRecipe.responseFile(flags))
|
||||
}
|
||||
|
||||
@@ -211,11 +211,12 @@ class LauncherActivity : Activity() {
|
||||
mainAction = when {
|
||||
running -> Action.Resume
|
||||
discStatus != GameStorage.DiscStatus.Ready -> Action.SelectDisc
|
||||
gameStatus != GameLibrary.Status.Ready -> Action.BuildGame
|
||||
gameStatus != GameLibrary.Status.Ready -> if (BuildConfig.ON_DEVICE_BUILD) Action.BuildGame else Action.ImportGame
|
||||
else -> Action.Play
|
||||
}
|
||||
secondaryAction = when {
|
||||
settingUp || running -> null
|
||||
mainAction == Action.ImportGame -> null
|
||||
gameStatus != GameLibrary.Status.Ready || mainAction == Action.SelectDisc -> Action.ImportGame
|
||||
else -> null
|
||||
}
|
||||
@@ -279,10 +280,14 @@ class LauncherActivity : Activity() {
|
||||
setup is GameSetup.State.Failed && building -> showBanner(getString(R.string.home_build_failed, setup.message), warning = true)
|
||||
setup is GameSetup.State.Failed -> showBanner(getString(R.string.home_setup_failed, setup.message), warning = true)
|
||||
discStatus == GameStorage.DiscStatus.Incomplete -> showBanner(getString(R.string.home_data_incomplete, disc), warning = true)
|
||||
gameStatus == GameLibrary.Status.Stale -> showBanner(getString(R.string.home_game_stale), warning = true)
|
||||
!GameStorage.modContentReady(this) ->
|
||||
showBanner(getString(R.string.home_mod_missing, GameStorage.modDirectory(this).absolutePath), warning = true)
|
||||
gameStatus == GameLibrary.Status.Stale ->
|
||||
showBanner(getString(if (BuildConfig.ON_DEVICE_BUILD) R.string.home_game_stale else R.string.home_game_stale_pc), warning = true)
|
||||
gameStatus == GameLibrary.Status.Missing && discStatus == GameStorage.DiscStatus.Missing ->
|
||||
showBanner(getString(R.string.home_setup_intro), warning = false)
|
||||
gameStatus == GameLibrary.Status.Missing -> showBanner(getString(R.string.home_game_missing), warning = false)
|
||||
showBanner(getString(if (BuildConfig.ON_DEVICE_BUILD) R.string.home_setup_intro else R.string.home_setup_intro_pc), warning = false)
|
||||
gameStatus == GameLibrary.Status.Missing ->
|
||||
showBanner(getString(if (BuildConfig.ON_DEVICE_BUILD) R.string.home_game_missing else R.string.home_game_missing_pc), warning = false)
|
||||
discStatus == GameStorage.DiscStatus.Missing -> showBanner(getString(R.string.home_data_missing, disc), warning = false)
|
||||
else -> showBanner(null)
|
||||
}
|
||||
@@ -322,7 +327,7 @@ class LauncherActivity : Activity() {
|
||||
|
||||
/** Builds the game on this headset from DATA, after saying what that takes. */
|
||||
private fun buildGame() {
|
||||
if (GameSetup.isRunning || GameStorage.discStatus(this) != GameStorage.DiscStatus.Ready) return
|
||||
if (!BuildConfig.ON_DEVICE_BUILD || GameSetup.isRunning || GameStorage.discStatus(this) != GameStorage.DiscStatus.Ready) return
|
||||
val message = if (GameLibrary.status(this) == GameLibrary.Status.Ready) R.string.home_build_replace_message else R.string.home_build_message
|
||||
confirm(R.string.home_build_title, message, R.string.home_build) {
|
||||
GameSetupService.startBuild(this)
|
||||
|
||||
@@ -282,11 +282,13 @@ class SettingsPage(
|
||||
GameLibrary.Status.Missing -> activity.getString(R.string.about_game_missing)
|
||||
}
|
||||
info(R.string.about_game, gameText, stacked = true)
|
||||
action(
|
||||
R.string.home_build, R.string.about_build_helper, R.string.home_build,
|
||||
enabled = !GameSetup.isRunning && GameStorage.discStatus(activity) == GameStorage.DiscStatus.Ready,
|
||||
) {
|
||||
buildGame()
|
||||
if (BuildConfig.ON_DEVICE_BUILD) {
|
||||
action(
|
||||
R.string.home_build, R.string.about_build_helper, R.string.home_build,
|
||||
enabled = !GameSetup.isRunning && GameStorage.discStatus(activity) == GameStorage.DiscStatus.Ready,
|
||||
) {
|
||||
buildGame()
|
||||
}
|
||||
}
|
||||
action(R.string.home_import, R.string.about_import_helper, R.string.home_import, enabled = !GameSetup.isRunning) {
|
||||
importGame()
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
<string name="home_setup_intro">Nothing from Mario Kart Wii comes with this app: the game is built from your own PAL (RMCP01) disc.\n\nOn this headset: copy your disc image (ISO, WBFS, RVZ…) here, for example into Download, press Select disc image, then Build on this Quest.\n\nWith a computer: in the WiiCompiled VR launcher, use Build for Quest with your disc image, copy the .wcgame file it makes to this headset, then press Import from computer.</string>
|
||||
<string name="home_game_missing">The game files are ready. Now build the game from them: Build on this Quest takes about 30–40 minutes on a Quest 3. With a computer, use Build for Quest in the WiiCompiled VR launcher instead, copy the .wcgame file to this headset and press Import from computer.</string>
|
||||
<string name="home_game_stale">This app was updated, so the game must be built again for it: press Build on this Quest, or use Build for Quest in the WiiCompiled VR launcher again and Import from computer.</string>
|
||||
<string name="home_setup_intro_pc">Nothing from Mario Kart Wii or Retro Rewind comes with this app: the game is built from your own PAL (RMCP01) disc.\n\nCopy your disc image (ISO, WBFS, RVZ…) here, for example into Download, and press Select disc image. Then, on a computer, use Build for Quest in the WiiCompiled VR launcher with this app’s APK, copy the .wcgame file it makes to this headset and press Import from computer.</string>
|
||||
<string name="home_game_missing_pc">The game files are ready. The game itself is built on a computer: in the WiiCompiled VR launcher, use Build for Quest with this app’s APK, copy the .wcgame file to this headset, then press Import from computer.</string>
|
||||
<string name="home_game_stale_pc">This app was updated, so the game must be built again for it: use Build for Quest in the WiiCompiled VR launcher again, then Import from computer.</string>
|
||||
<string name="home_build">Build on this Quest</string>
|
||||
<string name="home_build_preparing">Preparing…</string>
|
||||
<string name="home_building">Building %1$d%%</string>
|
||||
@@ -46,6 +49,7 @@
|
||||
<string name="home_replace_game_title">Replace the game?</string>
|
||||
<string name="home_replace_game_message">The game installed now stays until the new game file has been checked and completely imported.</string>
|
||||
<string name="home_data_incomplete">DATA is there but incomplete or unreadable. Select your disc image to extract it again, or copy the whole extracted partition (sys/ and files/, not just files/) to:\n%1$s</string>
|
||||
<string name="home_mod_missing">The Retro Rewind pack is missing, so the game cannot start. Copy the RetroRewind6 folder (the one with Binaries, Tracks and Assets in it) to:\n%1$s</string>
|
||||
<string name="home_checking">Checking disc…</string>
|
||||
<string name="home_extracting">Extracting %1$d%%</string>
|
||||
<string name="home_finishing">Finishing…</string>
|
||||
|
||||
@@ -22,6 +22,17 @@ class BuildRecipeTest {
|
||||
assertThrows(IllegalArgumentException::class.java) { BuildRecipe.expand("-I{workspace}", values) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun expandsAnyLinkSlotTheRecipeNames() {
|
||||
// Retro Rewind adds a mod slot; a builder only has to expand what the recipe lists.
|
||||
val arguments = BuildRecipe.linkArguments(
|
||||
listOf("{game:product}", "{game:mod}", "--start-lib", "{game:translated}", "--end-lib"),
|
||||
emptyMap(),
|
||||
mapOf("product" to listOf("/o/reg.o"), "mod" to listOf("/o/m1.o", "/o/m2.o"), "translated" to listOf("/o/base.o")),
|
||||
)
|
||||
assertEquals(listOf("/o/reg.o", "/o/m1.o", "/o/m2.o", "--start-lib", "/o/base.o", "--end-lib"), arguments)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun expandsTheLinkSlotsInPlace() {
|
||||
val template = listOf("-o", "{output}", "{kit}/objects/001_a.o", "{game:runtime}", "--start-lib", "{game:translated}", "--end-lib", "-lm")
|
||||
@@ -59,7 +70,10 @@ class BuildRecipeTest {
|
||||
)
|
||||
assertEquals(emptyList<String>(), BuildRecipe.sourceList(shards, "MKW_BASE_PORTABLE_SENSITIVE_SHARDS"))
|
||||
assertEquals(emptyList<String>(), BuildRecipe.sourceList(shards, "MKW_RETRO_MOD_SHARDS"))
|
||||
assertEquals(listOf("C:/w/generated/build_shards/base_registration/registration_00.cpp"), BuildRecipe.sourceList(shards, BuildRecipe.REGISTRATION_LIST))
|
||||
assertEquals(
|
||||
listOf("C:/w/generated/build_shards/base_registration/registration_00.cpp"),
|
||||
BuildRecipe.sourceList(shards, "MKW_BASE_REGISTRATION_SOURCES"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user