# Digimon World -- GameShark / PEC cheat codes
#
# HOW TO USE: flip [ ] to [x] to turn a cheat on, then start the game.
#             Flip it back to [ ] to turn it off. Nothing else to do.
#
# BACK UP YOUR MEMORY CARD FIRST if you care about the save. Several of these
# write fields the game stores on the card (stats, care state, items, story
# flags), so their effects survive being turned off again. The cards live in
# the `saves` folder next to the game; copying that folder is enough.
#
# ONE cheat ships enabled: "Skip all movies". Everything else is off.
#
# These are per-frame RAM pokes, exactly like a real GameShark: the runtime
# rewrites the target values every frame on guest VBlank. That is different
# from the Digimon World Trainer in the launcher's Mods panel, which patches
# the executable once before boot. Both can be used at the same time.
#
# CHEATS vs MODS: a cheat and a Mods-panel feature can both target the same
# thing, and then the cheat wins, because a mod patches the executable once
# before boot while a cheat rewrites RAM every frame afterwards. If a mod
# looks like it stopped working, check here first. Two cheats that write the
# same field are the same story: the later line in this file wins.
#
# Addresses below were cross-checked against the dw_decomp field map, and the
# `<- field` comments name the actual struct member each code writes:
#   PARTNER_PARA   @ 0x80138460  (raise-sim state: hunger, tiredness, ...)
#   PARTNER_ENTITY @ 0x801557A8  (stats, moves, lives)
# Two published codes had an off-by-one and are corrected here -- see
# "Weight Modifier" and "Age Modifier".
#
# Supported code types: 30 (write byte), 80 (write halfword),
#                       D0/D1/D2/D3 (halfword ==, !=, <, > gate next line),
#                       E0/E1/E2/E3 (byte     ==, !=, <, > gate next line),
#                       50 (repeat the NEXT write line: 5000CCSS, count CC,
#                           address step SS, and the value word is the
#                           per-iteration value step).
# A conditional gates only the line straight after it, so never put a D0/E0
# gate in front of a 50 -- it would consume the repeat and the write would
# happen once.
# Unsupported types are listed at the bottom with an explanation.


# ---------------------------------------------------------------- movies ----
# MOVIE_COMPLETED @ 0x80135304 -- the flag the movie player itself sets when a
# stream reaches its last frame (MOV_readFrame: `if (header->frameCount >=
# movie->frameCount) MOVIE_COMPLETED = 1;`). Holding it at 1 makes every movie
# finish on its first decoded frame, so the game tears the player down its own
# way instead of being interrupted.
#
# This is why it is a cheat and not a Mods-panel patch: this port is a STATIC
# recompilation, so patching an instruction in RAM does nothing -- the code was
# already compiled to native at build time. Per-frame DATA writes still work,
# which is what the GameShark engine does.
#
# Verified live: writing 01 here while the intro was playing ended it instantly.

[x] Skip all movies (boot straight to the title screen)
30135304 0001         # <- MOVIE_COMPLETED = 1

# ---------------------------------------------------------------- stats ----
# PARTNER_ENTITY + 0x38.. -- Stats.base / Stats.current in dw_decomp.

[ ] Max Offense (999)
801557E0 03E7          # <- stats.base.off

[ ] Max Defense (999)
801557E2 03E7          # <- stats.base.def

[ ] Max Speed (999)
801557E4 03E7          # <- stats.base.speed

[ ] Max Brains (999)
801557E6 03E7          # <- stats.base.brain

[ ] Brains 549 (keeps tech learning, avoids the high-brain MP glitch)
801557E6 0225          # <- stats.base.brain

[ ] Max HP (9999)
801557F0 270F          # <- stats.base.hp

[ ] Max MP (9999)
801557F2 270F          # <- stats.base.mp

[ ] Infinite HP
801557F4 270F          # <- stats.current.currentHP

[ ] Infinite MP
801557F6 270F          # <- stats.current.currentMP

[ ] Have All Attacks
30155800 00FF          # <- learnedMoves[0] byte 0
30155801 00FF
30155802 00FF
30155803 00FF
30155804 00FF          # <- learnedMoves[1]
30155805 00FF
30155806 00FF
30155807 00FF

[ ] Infinite Lives (3 hearts)
80155824 0003          # <- PartnerEntity.lives


# ------------------------------------------------------------ care state ----
# PARTNER_PARA @ 0x80138460.

[ ] Best Happiness
8013848A 0064          # <- happiness  (+0x2A) = 100, the clamp

[ ] Best Discipline
80138488 0064          # <- discipline (+0x28) = 100, the clamp

[ ] No Eating And No Pooping
80138478 00FF          # <- poopLevel  (+0x18)
8013849E 00FF          # <- foodLevel  (+0x3E)

[ ] Show tiredness in the virus bar
D0138482 0000          # if tiredness (+0x22) == 0
8013847E 0000          #   virusBar (+0x1E) = 0
D3138482 000A          # if tiredness > 10
8013847E 0002
D3138482 0014
8013847E 0004
D3138482 001E
8013847E 0006
D3138482 0028
8013847E 0008
D3138482 0032
8013847E 000A
D3138482 003C
8013847E 000C
D3138482 0046
8013847E 000E

[ ] Age 15 extends life by 300 hours
E01384AA 000F          # if age (+0x4A) == 15
801384A8 012C          #   remainingLifetime (+0x48) = 300

[ ] Weight Modifier -- 40
801384A2 0028          # <- weight (+0x42). CORRECTED: the published code
                       # writes 0x801384A3, the HIGH byte of this 16-bit
                       # field, so it never sets the weight you asked for.

[ ] Age Modifier -- 0
801384AA 0000          # <- age (+0x4A). CORRECTED for the same reason
                       # (published code targets 0x801384AB).


# -------------------------------------------------------------- training ----

# Training multipliers. trainBoostFlag (+0x4C) selects which stats are
# boosted; trainBoostValue (+0x4E) is the multiplier in tenths. The game
# gates purely on the flag, so the two MUST be set together -- a flag with
# value 0 zeroes every training gain. Enable only ONE of the three.
[ ] Training gains 2x (all six stats)
801384AC 003F          # <- trainBoostFlag: all six stats
801384AE 0014          # <- trainBoostValue: 20 = 2.0x

[ ] Training gains 3x (all six stats)
801384AC 003F
801384AE 001E          # <- 30 = 3.0x

[ ] Training gains 5x (all six stats)
801384AC 003F
801384AE 0032          # <- 50 = 5.0x

[ ] All gyms upgraded with baby bonus (+20%)
801384C6 000A
801384C8 000A
801384CA 000A
801384CE 000A
801384D0 000A
801384D2 000A          # <- Brain gym (upgradeBrainCounter, +0x72) -- was missing


# ---------------------------------------------------------------- world ----

[ ] Max Bits (money)
80134EB8 423F
80134EBA 000F          # <- 999999 = 0xF423F: the clamp value, and the exact
                       #    amount tamer.c checks to unlock the money medal

[ ] Max Merit Points
80134FC4 270F          # <- 9999, the actual clamp

[ ] Inventory size max (30 slots)
8013D4CE 001E

[ ] Can pick meat every hour
E0134EBE 0001
801BE050 0002

# RENAMED in 0.3.4. This writes DIGIMON_DATA[0].radius, and record 0 is named
# "main character" in the game's own table -- it is YOUR radius, not an
# enemy's. Enemies do notice you later, because the proximity test uses both
# radii (map.c:1260), but the same field is also the drop-shadow size
# (map.c:549) and half the interaction box (aabb.c:28), so your shadow
# disappears and reaching things can get fiddly. Kept, honestly labelled.
[ ] Shrink your own collision box (enemies notice you late; no shadow)
8012CECC 0000          # <- DIGIMON_DATA[0].radius, record 0 = the Tamer

# REMOVED in 0.3.4: "Speed up the game" wrote 0x80115A4C, which is libapi's
# internal _vsync_last. VSync REWRITES it unconditionally in its own epilogue
# (0x80091DE0), and the cheat engine only fires on vblank, so the poked value
# is never the value the frame lock reads. It did nothing. The real lever is a
# hardcoded immediate in gameLoop's VSync(3) call, which no data poke can
# reach; frame pacing belongs to the runtime, not to a cheat.

[ ] Auto-kill most enemies
80155874 0000
801559AC 0000
80155A14 0000
80155944 0000
80155AE4 0000
80155B4C 0000
801558DC 0000
80155A7C 0000

[ ] Have all cards
301BDFAC 0099
301BDFAD 0099
301BDFAE 0099
301BDFAF 0099
301BDFB0 0099
301BDFB1 0099
301BDFB2 0099
301BDFB3 0099
301BDFB4 0099
301BDFB5 0099
301BDFB6 0099
301BDFB7 0099
301BDFB8 0099
301BDFB9 0099
301BDFBA 0099
301BDFBB 0099
301BDFBC 0099
301BDFBD 0099
301BDFBE 0099
301BDFBF 0099
301BDFC0 0099
301BDFC1 0099
301BDFC2 0099
301BDFC3 0099
301BDFC4 0099
301BDFC5 0099
301BDFC6 0099
301BDFC7 0099
301BDFC8 0099
301BDFC9 0099
301BDFCA 0099
301BDFCB 0099
301BDFCC 0099

[ ] Have all medals
801BDFFA FFFF
801BDFFC FFFF

[ ] Have key items
301BDFD2 00FF          # <- trigger bits, not card nibbles: FF is correct here
301BDFD3 00FF

[ ] All Digimon on the chart / all evolved
301BE00D 00FF
801BE00E FFFF
801BE010 FFFF
801BE012 FFFF
301BE014 00FF

# REMOVED in 0.3.4. "All Digimon in city" wrote 0x801557C5 and 0x801557C6,
# which are the high byte of PARTNER_ENTITY.anim.animFrame (+0x1C) and
# anim.frameCount (+0x1E) -- the partner's ANIMATION counters, rewritten every
# frame. It had nothing to do with recruitment. "Everyone moves to File City"
# below does the real thing, through the recruit trigger flags.

# REMOVED in 0.3.4: "Monochromon shop points" wrote 0x8013500C, which has no
# symbol in the decompilation (MAIN_D_8013500C) and is written from nineteen
# places -- a working variable, not a persistent counter. The real merit
# counter is MERIT at 0x80134FC4, which "Max Merit Points" above already sets
# correctly. Shipping a poke at an unidentified address is exactly what this
# project's first golden rule forbids.


# ----------------------------------------------------------- known gaps ----
# These published codes use operations this engine does not implement. They
# are listed so nothing is silently lost -- enabling them prints a warning and
# skips only the unsupported lines.
#
# "HAPPY = DISCIPLINE" and "ENEMY GIVES MONEY = HP" open with type C2, a vendor
# extension whose exact semantics vary between GameShark revisions and PEC;
# implementing it from a guess would poke the wrong addresses. The long-form
# equivalents above cover the same ground.
#
# Type 50 IS supported and this note used to say otherwise. psx_cheats.c
# implements it (case 0x50): the address word is 0x50_00_CC_SS -- CC is the
# repeat count, SS the address step -- and the value word is the per-iteration
# value step, applied to the NEXT write line only. Four cheats above already
# rely on it.
#
# [ ] HAPPY = DISCIPLINE (unsupported: C2)
# C213848A 0002
# 80138488 0000
# D2138488 0032
# 80138488 0032
# D0138488 0064
# 80138488 0063
# E3138488 0064
# 80138488 0000


# ------------------------------------------------- more published codes ----
# Sourced from public GameShark lists for SLUS-01032 and cross-checked against
# the dw_decomp field map like everything above.

[ ] No Virus
8013847E 0000          # <- virusBar (+0x1E)

[ ] Perfect balance of happiness and discipline
8013848A 0064          # <- happiness  (+0x2A) = 100 (was 101, above the clamp)
80138488 0030          # <- discipline (+0x28) = 48

# CORRECTED in 0.3.4. The gate is an EQUALITY: battle_main.c:650 fires only
# when finisherProgress == finisherGoal, and battle_setup.c:100 sets
# finisherGoal = 3000 - Speed. The old value 0x0AF1 (2801) matched only at
# exactly Speed 199; at any other Speed it made Finish permanently
# UNavailable, the opposite of the name. Pinning both fields equal makes it
# hold at any Speed.
[ ] Always have Finish available
8013D658 0001          # <- fighter[0].finisherGoal     (+0x18)
8013D65A 0001          # <- fighter[0].finisherProgress (+0x1A), kept equal

[ ] Max quantity in all 10 item slots
50000A01 0000          # repeat 10x, address +1 each
3013D492 0063          #   <- inventory counts, slots 1..10 = 99

# The Item Keeper in File City holds one counter per item id, all 128 of them,
# so every item in the game has a permanent slot and the bank never runs out of
# room. 99 is the per-item maximum: 0x80 and above collides with a flag bit the
# list shares with the amount byte, and the rows misdraw.
#
# This replaces an earlier version that started 44 items too far in and reached
# only 56 of the 128, despite its comment claiming otherwise. Checked against
# the code: the list builder walks SCRIPT_STATE+0x54 one byte per id
# (0x8010744C addiu $a0,$v0,0x54 -- 0x801074AC lbu $a3,0($a0)) for 128 ids
# (0x80107574 sltiu $at,$a2,0x80), and deposit/withdraw load-modify-store that
# same field (0x80108788 / 0x80108794).
[ ] Item Keeper holds 99 of every item
50008001 0000          # repeat 128x, address +1 each, value +0
301BDF2C 0063          #   <- Item Keeper store, item ids 0..127 = 99
                       #      (SCRIPT_STATE+0x54, 0x801BDF2C..0x801BDFAB)

# 0x80135450 is the RAW pad word and it is ACTIVE LOW: PadRead (0x80091BF8)
# reads it and NORs it to make POLLED_INPUT, so the mask here is inverted.
#
# The bit layout is the PsyQ one, NOT the raw hardware mask, because the pad
# buffer is byte-swapped relative to it. Read it off the game's own code:
#   inventory.c:766-773  moveMenuCursor -- 0x1000 moves a menu cursor UP and
#                        0x4000 moves it DOWN, so those are the D-PAD
#   map.c:1272           newly-pressed 0x40 extends the interaction box along
#                        the way the player faces, so 0x40 is CROSS
#   script_common.c:404+ 0x40 confirm, 0x10 cancel, 0x1000/0x4000 up/down,
#                        0x800 Start
# So: Triangle 0x10, Circle 0x20, Cross 0x40, Square 0x80; Up 0x1000,
# Right 0x2000, Down 0x4000, Left 0x8000. "Circle alone" is therefore
# ~0x0020 = 0xFFDF.
#
# A 0.3.4 pass briefly changed this to 0xDFFF on the assumption that the
# hardware layout applied (Circle = 0x2000). It does not, and 0xDFFF is D-pad
# RIGHT -- which would have ended the run the first time the player walked
# right. Reverted the same day. The lesson is in docs/INTERNALS.md 6.4: read
# the code that CONSUMES the field, not a convention you expect it to follow.
[ ] Press O to beat the game
D0135450 FFDF          # if Circle (0x20 active-high), and only Circle, is held
80134EB4 0003          #   set the ending flag

# The gate is unchanged and correct -- Square is 0x80 active-high, so the raw
# active-low word is ~0x0080 = 0xFF7F. See the layout note above; a 0.3.4 pass
# briefly changed it to 0x7FFF (D-pad LEFT) and it was reverted.
#
# CORRECTED in 0.3.4: the old write set the entire low halfword -- hungry AND
# injured AND sick AND needs-toilet AND nine undefined bits -- in a field the
# game saves to the memory card. Only 0x08 means "needs the toilet".
[ ] Press Square to lay a pile of poop
D0135450 FF7F          # if Square (0x80 active-high), and only Square, is held
30138460 0008          #   <- PartnerPara.condition (+0x00), the poop bit only

# ------------------------------------------------------------ new digimon ----
# Requires the "Expanded Roster" mod (Mods panel) so slot 114 exists. Enable the
# cheat, then the body appears when the partner model next LOADS: quit to the
# title and Continue, or start a new game. (Saving in this game happens ONLY
# through Sleep -- menu -> Sleep when the partner shows the ZZZ bubble; the
# option is greyed out otherwise. There is no other way to save.) While the
# cheat is enabled the type is held, so disable it afterwards if you want
# evolution to work. Do not load savestates made before the mod was enabled --
# they restore the old tables.

[ ] Partner becomes Mugendramon (new, slot 114)
801557A8 0072          # <- PARTNER_ENTITY.entity.type

[ ] Partner becomes Machinedramon (the boss, 115)
801557A8 0073          # <- same mechanism, the original boss entry

# ===========================================================================
# Derived from the decompilation (jype0/dw_decomp), not from any published
# code list -- every address below was resolved to its symbol and its struct
# offset recomputed independently before shipping. None of these exist in the
# public GameShark corpus for this game.
# ===========================================================================

# -------- the raise sim ----------------------------------------------------

[ ] Freeze the game clock (no hunger, no aging, no lifespan drain)
30134E20 0000          # <- IS_GAMETIME_RUNNING; tickGameClock early-returns,
                       #    which stops hunger, tiredness, ageing and the
                       #    lifetime countdown all at once

[ ] Double game speed
301BE031 0001          # <- pstats[0] PSTAT_TIME_SPEED (1 = 2x, 2 = half)

[ ] Infinite lifespan
801384A8 03E7          # <- remainingLifetime (+0x48) held at 999 hours

[ ] Perfect condition (never sick, injured, sleepy or needing the toilet)
80138460 0000          # <- PartnerPara.condition (+0x00), u32. Bits: 0x01
80138462 0000          #    overdue for sleep, 0x02 very tired, 0x04 HUNGRY
                       #    (partner_impl.c:412 -- not "sleepy" as this said
                       #    before 0.3.4), 0x08 needs the toilet, 0x20
                       #    injured, 0x40 sick

[ ] Infinite training energy
8013849C 0050          # <- energyLevel (+0x3C) = 80, the highest energyCap
                       #    any Digimon has, so training is never refused

# -------- training ---------------------------------------------------------

# NOTE: this holds INVENTORY SLOT 0, overwriting whatever is in it, every
# frame. Anything you were carrying there is gone while the cheat is on, and
# the inventory is saved to the memory card. Put nothing you care about in the
# first slot.
[ ] Always carry the Training Manual (+10% to every training result)
3013D474 0021          # <- INVENTORY_ITEM_TYPES[0]   = 0x21 Trn. manual
3013D492 0063          # <- INVENTORY_ITEM_AMOUNTS[0] = 99
3013D4B0 0000          # <- INVENTORY_ITEM_NAMES[0]   = name row 0
3013D4CE 001E          # <- INVENTORY_SIZE = 30 (all three arrays must agree
                       #    or the slot shows as empty)

# -------- the city ---------------------------------------------------------

[ ] Everyone moves to File City (max prosperity, both gym upgrades)
50000801 0000          # <- repeat the next line 8 times, stepping 1 byte
301BDFE6 00FF          # <- recruit triggers 0xC8+id for every Digimon.
                       #    Two of these bits ARE the gym upgrades:
                       #    Kabuterimon -> +20% HP/Def/Speed gyms,
                       #    Kuwagamon   -> +20% Off/MP/Brain gyms.

[ ] Max prosperity points (100)
301BE032 0064          # <- pstats[1]

[ ] Item shop stocks 99 of everything
50004E01 0000          # <- repeat 78 times, stepping 1 byte
301BDEDE 0063          # <- smth[6..83], the shop's stock counters (cap 99)

# -------- battle -----------------------------------------------------------

# CORRECTED in 0.3.4. The old code pinned invulnerableTimer (+0x26), which is
# the post-hit recovery countdown, not a damage shield: battle_main.c:1032
# decrements it and only restores the fighter when it reaches 0, and the
# cleanup at :1592 runs only when it is <= 0. Holding it at 1000 meant the
# first hit still landed and the fighter then never left its hit reaction.
#
# Damage actually accumulates into hpDamageBuffer (+0x2E, battle_main.c:2192)
# and effective HP is currentHP - hpDamageBuffer everywhere it matters --
# battle_setup.c:329 and :379, battle_main.c:3686 -- with fainting tested as
# that difference reaching 0. Holding the buffer at zero is what "no damage"
# means.
[ ] Partner takes no damage
8013D66E 0000          # <- fighter[0].hpDamageBuffer (+0x2E), pending damage

[ ] No attack cooldown
8013D668 0000          # <- fighter[0].cooldown (+0x28)

[ ] Enemies drop 9999 bits each
80155888 270F          # <- NPC_ENTITIES[n].bits (+0x60), stride 0x68
801558F0 270F
80155958 270F
801559C0 270F
80155A28 270F
80155A90 270F
80155AF8 270F
80155B60 270F

# -------- medals and milestones -------------------------------------------

[ ] 100 fish caught (unlocks the fishing medal)
801384BE 0064          # <- fishCaught (+0x5E); tamer.c unlocks medal 9 at 100

[ ] Year 10 (unlocks the longevity medal)
30134F02 000A          # <- YEAR, u8; tamer.c unlocks medal 0x0E at exactly 10

[ ] Max tamer level (10)
301557A4 000A          # <- TAMER_ENTITY + 0x38, capped at 10 by tamer.c
