Skip to content

Commit

Permalink
feature: mra patching
Browse files Browse the repository at this point in the history
  • Loading branch information
MBDesu committed Sep 27, 2024
1 parent a857d4b commit 2932cd1
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 46 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
- [x] Encryption (`.bin` -> MAME, MAME -> MAME, `.bin` -> `.bin`) ✅ 2024-09-25
- [x] Splitting (`.bin` -> MAME) ✅ 2024-09-25
- [x] Diff between clean and modified ROMs to produce `.mra` patches automagically ✅ 2024-09-26
- [x] `.mra` patching ✅ 2024-09-27


### TODO

- [ ] Concatenating (MAME -> `.bin` \[decryption spits out a concatenated `maincpu` region, but that's the only concatenation exposed to the user right now])
- [ ] MAME <-> Darksoft conversion
- [ ] `.mra`/`.ips` patching
- [ ] Unshuffling graphics


Expand All @@ -44,6 +44,10 @@ Navigate to [Releases](https://github.com/MBDesu/mbdcps2/releases) and find the
-o </path/to/output/file>
Optional flag for specifying output file for operations that output a file
-p value
-r </path/to/clean/ROM.zip> -n <ROM set name> [-d] [-o </path/to/output.zip or .bin>]
Patch mode. Value supplied is the path to a .mra file. Patches a ROM with a .mra patch set. Default output file is <./<ROM set name>_modified.zip>
-r </path/to/ROM.zip> -n <ROM set name>
Required when using -d, -e, or -x. Specifies a ROM .zip file to open
Expand Down
62 changes: 62 additions & 0 deletions cps2rom/rom.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,70 @@ import (
"strings"

"github.com/MBDesu/mbdcps2/Resources"
file_utils "github.com/MBDesu/mbdcps2/utils"
)

func ValidateRomForRegion(romRegion RomRegion, zip *zip.ReadCloser) error {
requiredFiles := make([]string, 0, len(romRegion.Operations))
if len(romRegion.Operations) > 0 {
for _, operation := range romRegion.Operations {
if operation.Filename != "" {
requiredFiles = append(requiredFiles, operation.Filename)
}
}
}
hasFiles := make(map[string]bool)
for _, filename := range requiredFiles {
hasFiles[filename] = false
}
for _, file := range zip.File {
var name = file.Name
_, ok := hasFiles[name]
if ok {
hasFiles[name] = true
}
}

numMissingFiles := 0
missingFiles := make([]string, 0, len(requiredFiles))
for filename, hasFile := range hasFiles {
if !hasFile {
numMissingFiles = numMissingFiles + 1
missingFiles = append(missingFiles, filename)
}
}
if numMissingFiles > 0 {
return fmt.Errorf("missing %d files: %s", numMissingFiles, Resources.LogText.Bold(strings.Join(missingFiles, ", ")))
}
return nil
}

func SplitRegionToFiles(romRegion RomRegion, binary []byte, zipPath string) error {
f, err := file_utils.CreateFile(zipPath)
if err != nil {
return err
}
w := zip.NewWriter(f)
for _, operation := range romRegion.Operations {
Resources.Logger.Info(fmt.Sprintf("Writing %s from 0x%06x to 0x%06x...", operation.Filename, operation.Offset, operation.Offset+operation.Length))
regionBytes := binary[operation.Offset : operation.Offset+operation.Length]
fr, err := w.Create(operation.Filename)
if err != nil {
return err
}
_, err = fr.Write(regionBytes)
if err != nil {
return err
}
}
err = w.Close()
if err != nil {
return err
}
err = f.Close()
return err
}

func ValidateRomZip(romDefinition RomDefinition, zip *zip.ReadCloser) error {
var numFiles = len(romDefinition.Maincpu.Operations) + len(romDefinition.Audiocpu.Operations) + len(romDefinition.Gfx.Operations) + len(romDefinition.Qsound.Operations) + len(romDefinition.Key.Operations)
regions := []RomRegion{romDefinition.Audiocpu, romDefinition.Gfx, romDefinition.Maincpu, romDefinition.Qsound, romDefinition.Key}
Expand Down
150 changes: 150 additions & 0 deletions cps2rom/rompatcher.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package cps2rom

import (
"archive/zip"
"encoding/xml"
"errors"
"fmt"
"io"
"os"
"strconv"
"strings"

"github.com/MBDesu/mbdcps2/Resources"
file_utils "github.com/MBDesu/mbdcps2/utils"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
)
Expand All @@ -16,6 +22,71 @@ type RomPatch struct {
Data []uint8
}

type MraXml struct {
XMLName xml.Name `xml:"misterromdescription"`
Text string `xml:",chardata"`
About struct {
Text string `xml:",chardata"`
Author string `xml:"author,attr"`
Webpage string `xml:"webpage,attr"`
Source string `xml:"source,attr"`
Twitter string `xml:"twitter,attr"`
} `xml:"about"`
Name string `xml:"name"`
Setname string `xml:"setname"`
Rbf string `xml:"rbf"`
Mameversion string `xml:"mameversion"`
Year string `xml:"year"`
Manufacturer string `xml:"manufacturer"`
Players string `xml:"players"`
Joystick string `xml:"joystick"`
Rotation string `xml:"rotation"`
Region string `xml:"region"`
Platform string `xml:"platform"`
Category string `xml:"category"`
Catver string `xml:"catver"`
Mraauthor string `xml:"mraauthor"`
Rom []struct {
Text string `xml:",chardata"`
Index string `xml:"index,attr"`
Zip string `xml:"zip,attr"`
Type string `xml:"type,attr"`
Md5 string `xml:"md5,attr"`
Address string `xml:"address,attr"`
Part []struct {
Text string `xml:",chardata"`
Name string `xml:"name,attr"`
Crc string `xml:"crc,attr"`
Length string `xml:"length,attr"`
} `xml:"part"`
Patch []struct {
Data string `xml:",chardata"`
Offset string `xml:"offset,attr"`
} `xml:"patch"`
Interleave []struct {
Text string `xml:",chardata"`
Output string `xml:"output,attr"`
Part []struct {
Text string `xml:",chardata"`
Name string `xml:"name,attr"`
Crc string `xml:"crc,attr"`
Map string `xml:"map,attr"`
} `xml:"part"`
} `xml:"interleave"`
} `xml:"rom"`
Nvram struct {
Text string `xml:",chardata"`
Index string `xml:"index,attr"`
Size string `xml:"size,attr"`
} `xml:"nvram"`
Buttons struct {
Text string `xml:",chardata"`
Names string `xml:"names,attr"`
Default string `xml:"default,attr"`
Count string `xml:"count,attr"`
} `xml:"buttons"`
}

func createUint8ArrayFromUint16Array(arr []uint16) []uint8 {
newArr := make([]uint8, len(arr)*2)
for i := 0; i < len(arr); i++ {
Expand All @@ -28,6 +99,85 @@ func createUint8ArrayFromUint16Array(arr []uint16) []uint8 {
return newArr
}

func parseMra(mraFile *os.File) (*MraXml, error) {
mraBytes, err := io.ReadAll(mraFile)
if err != nil {
return nil, err
}
var mraXml MraXml
err = xml.Unmarshal(mraBytes, &mraXml)
if err != nil {
return nil, err
}
defer mraFile.Close()
return &mraXml, err
}

func mapOffsetToFile(offset int64, romRegion RomRegion) (string, int) {
for _, operation := range romRegion.Operations {
actualOffset := offset - 0x40
if actualOffset >= int64(operation.Offset) && offset < int64(operation.Offset+operation.Length) {
return operation.Filename, int(actualOffset - int64(operation.Offset))
}
}
return "", -1
}

func PatchRomRegionWithMra(romZip *zip.ReadCloser, mraFile *os.File, romRegion RomRegion, outputFilepath string) error {
Resources.Logger.Warn("Patching ROM...")
fileContentMap, err := file_utils.UnzipFilesToFilenameContentMap(romZip)
if err != nil {
return err
}
mra, err := parseMra(mraFile)
if err != nil {
return err
}
for _, rom := range mra.Rom {
lastOperationFilename := ""
for _, patch := range rom.Patch {
offset, err := strconv.ParseInt(patch.Offset, 0, 32)
if err != nil {
return err
}
data := make([]uint8, 0, len(patch.Data)*3)

for _, byteString := range strings.Split(patch.Data, " ") {
byte16, err := strconv.ParseInt(byteString, 16, 16)
if err != nil {
return err
}
byte8 := uint8(byte16 & 0xff)
data = append(data, byte8)
}
operationFilename, absoluteOffset := mapOffsetToFile(offset, romRegion)
if operationFilename != lastOperationFilename {
Resources.Logger.Info(fmt.Sprintf("Patching %s", operationFilename))
lastOperationFilename = operationFilename
}
for i, dataByte := range data {
fileContentMap[operationFilename][absoluteOffset+i] = dataByte
}
}
}
Resources.Logger.Done("Done patching ROM!")
Resources.Logger.Warn("Writing files to .zip...")
f, err := file_utils.CreateFile(outputFilepath)
if err != nil {
return err
}
w := *zip.NewWriter(f)
for file, content := range fileContentMap {
x, err := w.Create(file)
if err != nil {
return err
}
x.Write(content)
}
w.Close()
return err
}

func createUint16ArrayFromUint8Array(arr []uint8) []uint16 {
length := len(arr)
newArr := make([]uint16, length/2)
Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ module github.com/MBDesu/mbdcps2

go 1.23.1

require github.com/fatih/color v1.17.0
require (
github.com/fatih/color v1.17.0
github.com/jedib0t/go-pretty/v6 v6.5.9
)

require (
github.com/jedib0t/go-pretty/v6 v6.5.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
Expand All @@ -9,9 +11,15 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit 2932cd1

Please sign in to comment.