feat: hex patches

This commit is contained in:
2025-04-21 22:05:37 +00:00
parent b75cc8f240
commit e569d57788
9 changed files with 96 additions and 4 deletions

View File

@ -1,3 +1,7 @@
## 0.14.0
- Added the custom FREE PLAY patch for Verse
## 0.13.0 ## 0.13.0
- Added profile imports/exports - Added profile imports/exports

View File

@ -1,4 +1,7 @@
Looking for maimai DX players willing to help develop/test maimai DX support Looking for
- maimai DX players willing to help develop/test maimai DX support
- translators (any language other than English)
# STARTLINER # STARTLINER

View File

@ -1,5 +1,6 @@
### Short-term ### Short-term
- i18n
- https://gitea.tendokyu.moe/TeamTofuShop/segatools/issues/63 - https://gitea.tendokyu.moe/TeamTofuShop/segatools/issues/63
### Long-term ### Long-term

View File

@ -431,7 +431,7 @@ pub async fn export_profile(state: State<'_, Mutex<AppData>>, export_keychip: bo
} }
#[tauri::command] #[tauri::command]
pub async fn import_profile(state: State<'_, Mutex<AppData>>, path: PathBuf) -> Result<(), String> { pub async fn import_profile(path: PathBuf) -> Result<(), String> {
log::debug!("invoke: import_profile({:?})", path); log::debug!("invoke: import_profile({:?})", path);
Profile::import(path).map_err(|e| e.to_string()) Profile::import(path).map_err(|e| e.to_string())

View File

@ -42,6 +42,7 @@ pub struct Patch {
pub enum PatchData { pub enum PatchData {
Normal(NormalPatch), Normal(NormalPatch),
Number(NumberPatch), Number(NumberPatch),
Hex(HexPatch),
} }
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
@ -65,6 +66,12 @@ pub struct NumberPatch {
pub max: i32 pub max: i32
} }
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct HexPatch {
pub offset: u64,
pub off: Vec<u8>,
}
impl Serialize for Patch { impl Serialize for Patch {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer { where S: Serializer {
@ -83,6 +90,11 @@ impl Serialize for Patch {
state.serialize_field("size", &patch.size)?; state.serialize_field("size", &patch.size)?;
state.serialize_field("min", &patch.min)?; state.serialize_field("min", &patch.min)?;
state.serialize_field("max", &patch.max)?; state.serialize_field("max", &patch.max)?;
},
PatchData::Hex(patch) => {
state.serialize_field("type", "hex")?;
state.serialize_field("offset", &patch.offset)?;
state.serialize_field("off", &patch.off)?;
} }
} }
state.end() state.end()
@ -114,6 +126,23 @@ impl<'de> serde::Deserialize<'de> for Patch {
.ok_or_else(|| de::Error::missing_field("max"))? .ok_or_else(|| de::Error::missing_field("max"))?
).map_err(|_| de::Error::missing_field("max"))? ).map_err(|_| de::Error::missing_field("max"))?
}), }),
Some("hex") => {
let mut off_list = Vec::new();
for off in value.get("off").and_then(Value::as_array).unwrap() {
off_list.push(u8::try_from(
off.as_u64().ok_or_else(|| de::Error::missing_field("off"))?
).map_err(|_| de::Error::missing_field("off"))?);
}
// for off in value.get("off").and_then(Value::as_str).unwrap().bytes() {
// off_list.push(off);
// }
PatchData::Hex(HexPatch {
offset: value.get("offset")
.and_then(Value::as_u64)
.ok_or_else(|| de::Error::missing_field("offset"))?,
off: off_list
})
},
None => { None => {
let mut patches = vec![]; let mut patches = vec![];
for patch in value.get("patches").and_then(Value::as_array).unwrap() { for patch in value.get("patches").and_then(Value::as_array).unwrap() {

View File

@ -50,6 +50,21 @@ impl PatchSelection {
} else { } else {
log::error!("invalid number patch {:?}", patch); log::error!("invalid number patch {:?}", patch);
} }
},
PatchData::Hex(data) => {
if let PatchSelectionData::Hex(val) = sel {
res += &format!("{} F+{:X} ", filename, data.offset);
for byte in val {
res += &format!("{:02X}", byte);
}
res += " ";
for byte in &data.off {
res += &format!("{:02X}", byte);
}
} else {
log::error!("invalid number patch {:?}", patch);
}
} }
} }
format!("{}\n", res) format!("{}\n", res)

View File

@ -326,6 +326,26 @@
}, },
], ],
}, },
{
id: 'standard-custom-free-play-length',
type: 'number',
name: 'Custom FREE PLAY text length',
tooltip: 'Changes the length of the text displayed when Force FREE PLAY credit text is enabled',
danger: 'If this is longer than 11 characters, \"Force FREE PLAY credit text\" MUST be enabled.',
offset: 0x3875A9,
size: 1,
default: 9,
min: 0,
max: 27,
},
{
id: 'standard-custom-free-play-text',
type: 'hex',
name: 'Custom FREE PLAY text',
tooltip: 'Replace the FREE PLAY text when using Infinite credits',
offset: 0x1A5DFB4,
off: [0x46, 0x52, 0x45, 0x45, 0x20, 0x50, 0x4c, 0x41, 0x59],
},
], ],
}, },
{ {

View File

@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import InputNumber from 'primevue/inputnumber'; import InputNumber from 'primevue/inputnumber';
import InputText from 'primevue/inputtext';
import ToggleSwitch from 'primevue/toggleswitch'; import ToggleSwitch from 'primevue/toggleswitch';
import OptionRow from './OptionRow.vue'; import OptionRow from './OptionRow.vue';
import { usePrfStore } from '../stores'; import { usePrfStore } from '../stores';
@ -23,9 +25,26 @@ const setNumber = (key: string, val: number) => {
} }
}; };
defineProps({ const props = defineProps({
patch: Object as () => Patch, patch: Object as () => Patch,
}); });
// One day, I will repent
const hexModel = computed({
get() {
const hex = (prf.current!.data.patches[props.patch!.id!] as any)?.hex;
if (hex !== undefined) {
return new TextDecoder().decode(new Int8Array(hex).buffer);
} else {
return 'FREE PLAY';
}
},
set(value: string) {
(prf.current!.data.patches[props.patch!.id!] as any) = {
hex: new TextEncoder().encode(value),
};
},
});
</script> </script>
<template> <template>
@ -50,5 +69,6 @@ defineProps({
:max="patch?.max" :max="patch?.max"
:placeholder="(patch?.default ?? 0).toString()" :placeholder="(patch?.default ?? 0).toString()"
/> />
<InputText v-else-if="patch?.type === 'hex'" v-model="hexModel" />
</OptionRow> </OptionRow>
</template> </template>

View File

@ -167,7 +167,7 @@ export interface Patch {
id: string; id: string;
name: string; name: string;
tooltip: string; tooltip: string;
type: undefined | 'number'; type: undefined | 'number' | 'hex';
default: number; default: number;
min: number; min: number;
max: number; max: number;