Skip to content

Commit

Permalink
V7 (#1)
Browse files Browse the repository at this point in the history
* update internal imports to v7

* add magic timestamps used for game identification

* create game version consts

* choose validation strategy based on game version

* implement game version identification

* remove redundant parens

* implement chunk reading for HGSS

* improve logging

* improve logging

* create savefile abstractions

* create common savefile interface

* make savefiles implement ISave

* make block constructor

* add todo

* add temporary parsing constant

* add tests for new savefile interface

* add more metods to ISave, implement them per game variant

* remove unused packages (functionality merged into sav packge)

* merged in validator package functionality

* import ttypes from sav

* use new ISave interface

* update type comments

* add todos

* use indicated party size to fetch pokemon

* use indicated party size to fetch pokemon

* test fmt

* fix formatting

* fix formatting

* deleet problematic test

* update gitignore

* move constants to sav

* separate logically related functions into different files

* rename ISave methods, add more methods for write support

* update interface to use ISave methods
  • Loading branch information
dingdongg authored Jun 19, 2024
1 parent c6b6bbd commit 1035f80
Show file tree
Hide file tree
Showing 24 changed files with 480 additions and 230 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ pkmn-rom-parser
dev/*

main
results*
results*

sav/*_test.go
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Application used to parse in-game information in generation IV/V Pokemon games.

## Installation
```sh
go get github.com/dingdongg/pkmn-rom-parser/v6
go get github.com/dingdongg/pkmn-rom-parser/v7
```

## Usage
Expand Down
1 change: 1 addition & 0 deletions consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package consts
const BLOCK_SIZE_BYTES = 32
const PARTY_POKEMON_SIZE = 236
const PERSONALITY_OFFSET = 0xA0
const PERSONALITY_OFFSET_HGSS = 0x98

const (
BLOCK_A_ITEM = 0x2
Expand Down
9 changes: 9 additions & 0 deletions consts/gamever/gamever.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gamever

type GameVer int

const (
DP GameVer = iota
PLAT
HGSS
)
4 changes: 2 additions & 2 deletions crypt/crypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import (
"encoding/binary"
"log"

"github.com/dingdongg/pkmn-rom-parser/v6/prng"
"github.com/dingdongg/pkmn-rom-parser/v7/prng"
)

// Computes a checksum via the CRC16-CCITT algorithm on the given data
func CRC16_CCITT(data []byte) uint16 {
sum := uint(0xFFFF)

for _, b := range data {
sum = (sum << 8) ^ seeds[b^byte((sum>>8))]
sum = (sum << 8) ^ seeds[b^byte(sum>>8)]
}

return uint16(sum)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/dingdongg/pkmn-rom-parser/v6
module github.com/dingdongg/pkmn-rom-parser/v7

go 1.22.3

Expand Down
2 changes: 1 addition & 1 deletion items/items.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"strconv"
"strings"

"github.com/dingdongg/pkmn-rom-parser/v6/path_resolver"
"github.com/dingdongg/pkmn-rom-parser/v7/path_resolver"
)

// fetch item names and cache in RAM to prevent multiple file IO operations
Expand Down
25 changes: 11 additions & 14 deletions parser.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
package parser

import (
"github.com/dingdongg/pkmn-rom-parser/v6/consts"
"github.com/dingdongg/pkmn-rom-parser/v6/rom_reader"
"github.com/dingdongg/pkmn-rom-parser/v6/rom_writer"
"github.com/dingdongg/pkmn-rom-parser/v6/rom_writer/req"
"github.com/dingdongg/pkmn-rom-parser/v6/validator"
"github.com/dingdongg/pkmn-rom-parser/v6/validator/locator"
"github.com/dingdongg/pkmn-rom-parser/v7/rom_reader"
"github.com/dingdongg/pkmn-rom-parser/v7/rom_writer"
"github.com/dingdongg/pkmn-rom-parser/v7/rom_writer/req"
"github.com/dingdongg/pkmn-rom-parser/v7/sav"
)

func Parse(savefile []byte) ([]rom_reader.Pokemon, error) {
if err := validator.Validate(savefile); err != nil {
game, err := sav.Validate(savefile)
if err != nil {
return []rom_reader.Pokemon{}, err
}

chunk := locator.GetLatestSaveChunk(savefile)
partyData := chunk.SmallBlock.BlockData[consts.PERSONALITY_OFFSET:]

return rom_reader.GetPartyPokemon(partyData), nil
partyData := rom_reader.GetPartyPokemon(game)
return partyData, nil
}

func Write(savefile []byte, newBytes []req.WriteRequest) ([]byte, error) {
if err := validator.Validate(savefile); err != nil {
game, err := sav.Validate(savefile)
if err != nil {
return []byte{}, err
}

chunk := locator.GetLatestSaveChunk(savefile)
return rom_writer.UpdatePartyPokemon(savefile, *chunk, newBytes)
return rom_writer.UpdatePartyPokemon(game, newBytes)
}
32 changes: 25 additions & 7 deletions rom_reader/rom_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package rom_reader

import (
"encoding/binary"
"fmt"
"log"

"github.com/dingdongg/pkmn-rom-parser/v6/char"
"github.com/dingdongg/pkmn-rom-parser/v6/consts"
"github.com/dingdongg/pkmn-rom-parser/v6/crypt"
"github.com/dingdongg/pkmn-rom-parser/v6/data"
"github.com/dingdongg/pkmn-rom-parser/v6/shuffler"
"github.com/dingdongg/pkmn-rom-parser/v7/char"
"github.com/dingdongg/pkmn-rom-parser/v7/consts"
"github.com/dingdongg/pkmn-rom-parser/v7/crypt"
"github.com/dingdongg/pkmn-rom-parser/v7/data"
"github.com/dingdongg/pkmn-rom-parser/v7/sav"
"github.com/dingdongg/pkmn-rom-parser/v7/shuffler"
)

type Stats struct {
Expand Down Expand Up @@ -36,17 +38,33 @@ type Pokemon struct {
IVs Stats
}

func (p Pokemon) String() string {
return fmt.Sprintf(`
Pokemon {
%s (#%d) Level: %d
%+v
held item (id): %s
Nature: %s
Ability: %s
EV: %+v
IV: %+v
}`, p.Name, p.PokedexId, p.BattleStat.Level, p.BattleStat.Stats, p.Item, p.Nature, p.Ability, p.EVs, p.IVs)
}

const (
A uint = iota
B uint = iota
C uint = iota
D uint = iota
)

func GetPartyPokemon(ciphertext []byte) []Pokemon {
// TODO: update function to use ISave methods instead
func GetPartyPokemon(game sav.ISave) []Pokemon {
size := game.PartySize()
ciphertext := game.PartySection()
var party []Pokemon

for i := uint(0); i < 6; i++ {
for i := uint(0); i < uint(size); i++ {
party = append(party, parsePokemon(ciphertext, i))
}

Expand Down
2 changes: 1 addition & 1 deletion rom_reader/rom_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"
"testing"

"github.com/dingdongg/pkmn-rom-parser/v6/consts"
"github.com/dingdongg/pkmn-rom-parser/v7/consts"
"github.com/google/go-cmp/cmp"
)

Expand Down
4 changes: 2 additions & 2 deletions rom_writer/req/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"fmt"
"log"

"github.com/dingdongg/pkmn-rom-parser/v6/char"
"github.com/dingdongg/pkmn-rom-parser/v6/data"
"github.com/dingdongg/pkmn-rom-parser/v7/char"
"github.com/dingdongg/pkmn-rom-parser/v7/data"
)

func (wr WriteRequest) WriteItem(itemName string) {
Expand Down
4 changes: 2 additions & 2 deletions rom_writer/req/req.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package req
import (
"fmt"

"github.com/dingdongg/pkmn-rom-parser/v6/consts"
"github.com/dingdongg/pkmn-rom-parser/v6/shuffler"
"github.com/dingdongg/pkmn-rom-parser/v7/consts"
"github.com/dingdongg/pkmn-rom-parser/v7/shuffler"
)

const (
Expand Down
4 changes: 2 additions & 2 deletions rom_writer/req/req_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"encoding/binary"
"testing"

"github.com/dingdongg/pkmn-rom-parser/v6/char"
"github.com/dingdongg/pkmn-rom-parser/v6/tutil"
"github.com/dingdongg/pkmn-rom-parser/v7/char"
"github.com/dingdongg/pkmn-rom-parser/v7/tutil"
)

var templates = tutil.GetTemplates()
Expand Down
35 changes: 8 additions & 27 deletions rom_writer/req/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type Writable interface {
Bytes() ([]byte, error)
}

// for battle stats
// for battle stats. implements Writable
type WriteStats struct {
Hp uint
Attack uint
Expand All @@ -14,45 +14,26 @@ type WriteStats struct {
Speed uint
}

type WriteEffortValue struct {
Hp uint
Attack uint
Defense uint
SpAttack uint
SpDefense uint
Speed uint
}
// implements Writable
type WriteEffortValue WriteStats

type WriteIndivValue struct {
Hp uint
Attack uint
Defense uint
SpAttack uint
SpDefense uint
Speed uint
}
// implements Writable

// for IDs/level
type WriteIndivValue WriteStats

// for IDs/level. implements Writable
type WriteUint struct {
Val uint
NumBytes uint // number of bytes. Used in Bytes() implementation
}

// for nicknames
// for nicknames. implements Writable
type WriteString struct {
Val string
}

type NewData map[string]Writable

// types that implement CompressibleStat can have their stat values
// compressed into an unsigned integer. For instance,
// each IV stat uses 5 bits (=30) - so IVs can be packed in a uint32.
// each EV stat uses 8 bits (=48) - so EVs can be packed in a uint64.
type CompressibleStat interface {
Compress(elemBits uint) uint
}

type WriteRequest struct {
PartyIndex uint
Contents NewData
Expand Down
37 changes: 21 additions & 16 deletions rom_writer/rom_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"encoding/binary"
"fmt"

"github.com/dingdongg/pkmn-rom-parser/v6/consts"
"github.com/dingdongg/pkmn-rom-parser/v6/crypt"
"github.com/dingdongg/pkmn-rom-parser/v6/rom_writer/req"
"github.com/dingdongg/pkmn-rom-parser/v6/shuffler"
"github.com/dingdongg/pkmn-rom-parser/v6/validator"
"github.com/dingdongg/pkmn-rom-parser/v7/consts"
"github.com/dingdongg/pkmn-rom-parser/v7/crypt"
"github.com/dingdongg/pkmn-rom-parser/v7/rom_writer/req"
"github.com/dingdongg/pkmn-rom-parser/v7/sav"
"github.com/dingdongg/pkmn-rom-parser/v7/shuffler"
)

/*
Expand Down Expand Up @@ -70,9 +70,12 @@ func (wrb *WriteRequestBuilder) AddRequest(partyIndex uint) (req.WriteRequest, e
return request, nil
}

func UpdatePartyPokemon(savefile []byte, chunk validator.Chunk, newData []req.WriteRequest) ([]byte, error) {
// TODO: update function to use ISave methods instead
func UpdatePartyPokemon(savefile sav.ISave, newData []req.WriteRequest) ([]byte, error) {
updatedPokemonIndexes := make(map[uint]bool, 0)
base := chunk.SmallBlock.Address + consts.PERSONALITY_OFFSET

latestChunk := savefile.LatestData()
base := latestChunk.SmallBlock.Address + savefile.PartyOffset()
changes := make(StagingMap)

for _, wr := range newData {
Expand All @@ -83,7 +86,7 @@ func UpdatePartyPokemon(savefile []byte, chunk validator.Chunk, newData []req.Wr
}

offset := base + wr.PartyIndex*consts.PARTY_POKEMON_SIZE
personality := binary.LittleEndian.Uint32(savefile[offset : offset+4])
personality := binary.LittleEndian.Uint32(savefile.Get(offset, 4))

dataOffset, blockIndex, err := req.GetWriteLocation(request)
if err != nil {
Expand All @@ -100,7 +103,8 @@ func UpdatePartyPokemon(savefile []byte, chunk validator.Chunk, newData []req.Wr
}

if _, ok := changes[wr.PartyIndex]; !ok {
changes[wr.PartyIndex] = crypt.DecryptPokemon(savefile[offset:])
encryptedPokemon := savefile.Get(offset, consts.PARTY_POKEMON_SIZE)
changes[wr.PartyIndex] = crypt.DecryptPokemon(encryptedPokemon)
}
size := copy(changes[wr.PartyIndex][blockAddress+uint(dataOffset):], bytes)
if size != len(bytes) {
Expand All @@ -117,17 +121,18 @@ func UpdatePartyPokemon(savefile []byte, chunk validator.Chunk, newData []req.Wr
pokemonOffset := base + i*consts.PARTY_POKEMON_SIZE
encrypted := crypt.EncryptPokemon(changes[i])

copy(savefile[AbsAddress(pokemonOffset):], encrypted)
copy(savefile.Get(pokemonOffset, consts.PARTY_POKEMON_SIZE), encrypted)
}

updateBlockChecksum(savefile, chunk)
return savefile, nil
updateBlockChecksum(savefile)
return savefile.Data(), nil
}

func updateBlockChecksum(savefile []byte, chunk validator.Chunk) {
func updateBlockChecksum(savefile sav.ISave) {
chunk := savefile.LatestData()
start := chunk.SmallBlock.Address
end := chunk.SmallBlock.Address + uint(chunk.SmallBlock.Footer.BlockSize) - 0x14
newChecksum := crypt.CRC16_CCITT(savefile[start:end])
checksumDataSize := uint(chunk.SmallBlock.Footer.BlockSize) - 0x14
newChecksum := crypt.CRC16_CCITT(savefile.Get(start, checksumDataSize))

binary.LittleEndian.PutUint16(savefile[end+18:], newChecksum)
binary.LittleEndian.PutUint16(savefile.Get(start+checksumDataSize+0x12, 2), newChecksum)
}
Loading

0 comments on commit 1035f80

Please sign in to comment.