Skip to content

Commit fe4a663

Browse files
committed
new RNG code
1 parent 9afbfb7 commit fe4a663

File tree

3 files changed

+125
-2
lines changed

3 files changed

+125
-2
lines changed

FF1Lib/FF1Rom.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public void Randomize(Blob seed, Flags flags, Preferences preferences)
106106
FixWarpBug(); // The warp bug must be fixed for magic level shuffle and spellcrafter
107107
SeparateUnrunnables();
108108
UpdateDialogs();
109-
MoveSmokeSpriteVariables();
109+
ReplaceBattleRNG(rng);
110110

111111
flags = Flags.ConvertAllTriState(flags, rng);
112112

FF1Lib/Hacks.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,16 @@ public partial class FF1Rom : NesRom
4545
public const int SmokeSpriteReplaceStart = 0x317E9;
4646
public const int SmokeSpriteReplaceEnd = 0x31A2C;
4747

48-
public void MoveSmokeSpriteVariables()
48+
public const int BattleRNGOffset = 0x7FCE7;
49+
50+
public void ReplaceBattleRNG(MT19337 rng)
4951
{
5052
// This moves some temporary memory locations used to draw the smoke effect sprites
5153
// in battle to the same locations used to store attacker stats. These can overwrite
5254
// each other without issue, and it frees up some space for a few bytes of RNG state.
5355
var smokeSpriteCode = Get(SmokeSpriteReplaceStart, SmokeSpriteReplaceEnd - SmokeSpriteReplaceStart);
5456

57+
// We only need 4 bytes, and moving the others seems to mess some stuff up.
5558
smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68AF }), Blob.FromUShorts(new ushort[] { 0x686C }));
5659
smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B0 }), Blob.FromUShorts(new ushort[] { 0x686D }));
5760
smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B1 }), Blob.FromUShorts(new ushort[] { 0x686E }));
@@ -61,6 +64,14 @@ public void MoveSmokeSpriteVariables()
6164
//smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B5 }), Blob.FromUShorts(new ushort[] { 0x6872 }));
6265

6366
Put(SmokeSpriteReplaceStart, smokeSpriteCode);
67+
68+
// LCG.asm
69+
Put(BattleRngOffset, Blob.FromHex("ADAF68AAAD57FD205FFD186D5BFD9020E8188DAF688A8DB368ADB068AAAD58FD205FFD186D5CFD9002E8186DB3689002E8188DB0688A8DB368ADB168AAAD59FD205FFD186D5DFD9002E8186DB3689002E8188DB1688A8DB368ADB268AAAD5AFD205FFD186D5EFD186DB368188DB26860054B56AC000000008DB3688EB468A208A9008DB5684EB3689004186DB4686A6EB568CAD0F0AAADB56860"));
70+
71+
// Choose a random odd number for c in the LCG.
72+
uint c = rng.Next();
73+
c |= 0x00000001;
74+
Put(BattleRngOffset + 4, Blob.FromUInts(new[] { c }));
6475
}
6576

6677
// Required for npc quest item randomizing

FF1Lib/asm/LCG.asm

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,116 @@
44

55
; A research paper on selecting good parameters for the multiplier:
66
; https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.34.1024&rep=rep1&type=pdf
7+
; We'll use m = 2891336453, or 0xAC564B05 from Table 4.
8+
; Any odd integer will do for c, so we'll take a random value.
79

10+
11+
12+
* = $FCE7
13+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
14+
;; Bank 0F, $FCE7 (BattleRNG) ;;
15+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
16+
17+
battle_rng_state = $68AF
18+
19+
BattleRNG:
20+
LDA battle_rng_state ; Get the first byte of state
21+
TAX
22+
LDA battle_rng_m ; Get the first byte of m
23+
JSR MultiplyXA ; Multiply state by m
24+
CLC
25+
ADC battle_rng_c ; Add c
26+
BCC :+
27+
INX ; Increment high bits if necessary
28+
CLC
29+
:
30+
STA battle_rng_state ; Store the low bits back to state
31+
TXA
32+
STA btltmp_multA ; Save the high bits for the next step
33+
34+
LDA battle_rng_state + 1 ; Now do it again for the next byte of state
35+
TAX
36+
LDA battle_rng_m + 1
37+
JSR MultiplyXA
38+
CLC
39+
ADC battle_rng_c + 1
40+
BCC :+
41+
INX
42+
CLC
43+
:
44+
ADC btltmp_multA ; Add the high bits from the previous step
45+
BCC :+
46+
INX
47+
CLC
48+
:
49+
STA battle_rng_state + 1
50+
TXA
51+
STA btltmp_multA
52+
53+
LDA battle_rng_state + 2
54+
TAX
55+
LDA battle_rng_m + 2
56+
JSR MultiplyXA
57+
CLC
58+
ADC battle_rng_c + 2
59+
BCC :+
60+
INX
61+
CLC
62+
:
63+
ADC btltmp_multA
64+
BCC :+
65+
INX
66+
CLC
67+
:
68+
STA battle_rng_state + 2
69+
TXA
70+
STA btltmp_multA
71+
72+
LDA battle_rng_state + 3 ; Last byte
73+
TAX
74+
LDA battle_rng_m + 3
75+
JSR MultiplyXA
76+
CLC
77+
ADC battle_rng_c + 3
78+
CLC ; No need to save the high bits, so just CLC
79+
ADC btltmp_multA
80+
CLC ; Just in case
81+
STA battle_rng_state + 3
82+
83+
; And we're done. A already has the highest bits of state, and that's what we want to return.
84+
RTS
85+
86+
battle_rng_m:
87+
.BYTE $05, $4B, $56, $AC ; m
88+
battle_rng_c:
89+
.BYTE $00, $00, $00, $00 ; c (this will be replaced by the randomizer)
90+
91+
92+
93+
; MultiplyXA copied from bank 0B
94+
btltmp_multA = $68B3
95+
btltmp_multB = $68B4
96+
btltmp_multC = $68B5
97+
98+
MultiplyXA:
99+
STA btltmp_multA ; store the values we'll be multiplying
100+
STX btltmp_multB
101+
LDX #$08 ; Use x as a loop counter. X=8 for 8 bits
102+
103+
LDA #$00 ; A will be the high byte of the product
104+
STA btltmp_multC ; multC will be the low byte
105+
106+
; For each bit in multA
107+
@Loop:
108+
LSR btltmp_multA ; shift out the low bit
109+
BCC :+
110+
CLC ; if it was set, add multB to our product
111+
ADC btltmp_multB
112+
: ROR A ; then rotate down our product
113+
ROR btltmp_multC
114+
DEX
115+
BNE @Loop
116+
117+
TAX ; put high bits of product in X
118+
LDA btltmp_multC ; put low bits in A
119+
RTS

0 commit comments

Comments
 (0)