Advertisement
Zeronoin

A Nightmare on Elm Street NES Door RNG FCEUX Lua Script

Aug 13th, 2016
130
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.17 KB | None | 0 0
  1. -- A Nightmare on Elm Street NES Door RNG FCEUX Lua Script
  2. -- by ZeroNoin
  3.  
  4.  
  5. -- The document explaining more about the manipulation is here:
  6. -- http://pastebin.com/diLdmYQP
  7.  
  8. -- The RNG of the doors when a game starts in A Nightmare on Elm Street NES has a big effect on a speedrun.
  9. -- What really helps here is a RNG buffer/manipulation to get the ideal RNG every or about every time.
  10.  
  11. -- The entrance to the first three stages are randomly shuffled when a game is started as the player number screen fades.
  12. -- The value for the resulting RNG is stored in $06F7, ranging from 0x00 to 0x05.
  13. -- A value of 0x00 is best because it goes in order of the doors left to right.
  14. -- A value of 0x01 is worst because it has the player do the first stage before finding the run is dead.
  15. -- Values ranging from 0x02 to 0x05 quickly signal the speedrunner to reset.
  16.  
  17. -- The frame counter is stored in $0021.
  18. -- The controller inputs affecting RNG are stored in $0030, $0031, and $0032.
  19. -- The relevant RNG is stored in $06FD, and is updated each frame by adding the frame counter value and the controller inputs value.
  20. -- These addresses are uninitialized at Power On, meaning different consoles and emulators may need different manipulation inputs.
  21.  
  22. -- The RNG updates most frames from when the game is Powered On.
  23. -- There is a black screen following the first text screen when the game is powered on.
  24. -- This black screen has 10 frames of loading and lagging, and the RNG value does not update during these frames.
  25. -- Start can be pressed and held during this black screen all the way through the title and player number screens to a game start.
  26. -- This black screen is very useful because an 11-frame window buffer can be created using this.
  27.  
  28. -- A buffer can be created from Power On, and not from a Reset because the RNG does not clear on a Reset.
  29. -- Adding held buttons and/or directions from Power On can help set the RNG to a more advantageous place.
  30.  
  31. -- This script is for finding all the input buffers utilitizing Power-On and the 11-frame window to create the ideal RNG.
  32. -- Note the test can be sped up by pressing the + key.
  33. -- It is also nice to see what inputs are happening using the option Config > Display > Input Display > 1 Player.
  34. -- Make sure the controller inputs are clear and that you do not press any inputs during the test.
  35.  
  36.  
  37. -- Five addresses affecting door RNG and five values to initialize them at Power On.
  38. -- On FCEUX and likely on most or all Toploader NES's, the Counter and Inputs begin at 0x00, and the RNG begins at 0xFF.
  39. -- On BizHawk all memory addresses begin at 0xFF.
  40. -- On Frontloader NES's it varies quite a bit often depending on the contents of the addresses before and the timing of the Power On.
  41. COUNTER_ADDRESS = 0x0021
  42. COUNTER_SET = 0x00
  43. INPUT1_ADDRESS = 0x0030
  44. INPUT1_SET = 0x00
  45. INPUT2_ADDRESS = 0x0031
  46. INPUT2_SET = 0x00
  47. INPUT3_ADDRESS = 0x0032
  48. INPUT3_SET = 0x00
  49. RNG_ADDRESS = 0x06FD
  50. RNG_SET = 0xFF
  51.  
  52. -- The address where the result of the door RNG is stored, wanting 0x00.
  53. RESULT_ADDRESS = 0x06F7
  54. RESULT_SUCCESS = 0x00
  55.  
  56. -- A number of frames after Power On during the black screen in which Start can be held through to start a game.
  57. BLACK_FRAME = 325
  58.  
  59. -- A number of frames after Power On soon after the RNG is loaded into the doors address $06F7.
  60. RNG_FRAME = 440
  61.  
  62. -- The current inputs for Power On and the black screen ranging 0 to 255 (0x00 to 0xFF) for all possible input values used by the NES.
  63. -- These values increment as the script tests all the possible sets of inputs.
  64. CURRENT_POWER = 0x00
  65. CURRENT_BLACK = 0x00
  66.  
  67. -- List of successful input buffers in strings formatted "|ABSTUDLR||ABSTUDLR|".
  68. RESULTS = {}
  69.  
  70.  
  71. -- The FCEUX RAM address read function:
  72. -- memory.readbyte(int address)
  73. -- Compare the value at the result address with the value considered RNG success.
  74. function compare_result(address,value)
  75.     if(memory.readbyte(address) == value) then
  76.         return true
  77.     else
  78.         return false
  79.     end
  80. end
  81.  
  82.  
  83. -- Transform an input integer into a bit table formatted {1,1,1,1,1,1,1,1}.
  84. -- The values correspond to {A,B,S,T,U,D,L,R}.
  85. -- Copy the global variable into a temporary variable so the global variable is not modified.
  86. -- Compare to the individual input values 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, and 0x01.
  87. -- Subtract if it contains that value and put 1 in the table, otherwise put 0 in the table.
  88. function to_bit_table(input)
  89.     temp = input
  90.     output = {}
  91.     current = 0x80
  92.     while(current >= 0x01) do
  93.         if(temp >= current) then
  94.             temp = temp - current
  95.             table.insert(output,1)
  96.         else
  97.             table.insert(output,0)
  98.         end
  99.         current = current / 2
  100.     end
  101.     return output
  102. end
  103.  
  104.  
  105. -- The FCEUX functions for creating inputs:
  106. -- joypad.set(int player, table input)
  107. -- The FCEUX table input key values are:
  108. -- A B select start up down left right
  109. INPUT_TABLE = {"A", "B", "select", "start", "up", "down", "left", "right"}
  110. -- The commonly-used corresponding abbreviations for the inputs:
  111. INPUT_ABBR = {"A","B","S","T","U","D","L","R"}
  112. -- Create a new table with all the inputs set to false, then check each input and set it to true if it is present.
  113. -- Then set the input table to the new table.
  114. function input_buttons(input)
  115.     buttons = {A=false,B=false,select=false,start=false,up=false,down=false,left=false,right=false}
  116.     for i=1,8,1 do
  117.         if(input[i] == 1) then
  118.             buttons[INPUT_TABLE[i]] = true
  119.         end
  120.     end
  121.     joypad.set(1,buttons)
  122. end
  123.  
  124.  
  125. -- Convert the input table values into their corresponding abbreviations in a readable string.
  126. -- If the value is present add its abbreviation, otherwise add a space.
  127. function bits_to_abbr(input)
  128.     output = ""
  129.     for i=1,8,1 do
  130.         if(input[i] == 1) then
  131.             output = output .. INPUT_ABBR[i]
  132.         else
  133.             output = output .. " "
  134.         end
  135.     end
  136.     return output
  137. end
  138.  
  139.  
  140. -- Filter the Power On input for opposing directions or the Start button.
  141. -- The Start button should be off for the Power On and on for the black screen.
  142. function power_filter(input)
  143.     output = true
  144.     if(input[5] == 1 and input[6] == 1) then
  145.         output = false
  146.     end
  147.     if(input[7] == 1 and input[8] == 1) then
  148.         output = false
  149.     end
  150.     if(input[4] == 1) then
  151.         output = false
  152.     end
  153.     return output
  154. end
  155.  
  156.  
  157. -- Filter the black screen input for opposing directions or no Start button.
  158. -- The Start button should be off for the Power On and on for the black screen.
  159. function black_filter(input)
  160.     output = true
  161.     if(input[5] == 1 and input[6] == 1) then
  162.         output = false
  163.     end
  164.     if(input[7] == 1 and input[8] == 1) then
  165.         output = false
  166.     end
  167.     if(input[4] == 0) then
  168.         output = false
  169.     end
  170.     return output
  171. end
  172.  
  173.  
  174. -- The FCEUX console print command:
  175. -- emu.print(string str)
  176. -- Make successful pairs of inputs into a string formatted "|ABSTUDLR||ABSTUDLR|".
  177. -- Store these successful inputs and print them in the FCEUX console.
  178. function report(input1,input2)
  179.     output = "|" .. bits_to_abbr(input1) .. "||" .. bits_to_abbr(input2) .. "|"
  180.     table.insert(RESULTS,output)
  181.     emu.print(output)
  182. end
  183.  
  184.  
  185. -- The FCEUX Power On command:
  186. -- emu.poweron()
  187. -- The FCEUX memory address write command:
  188. -- memory.writebyte(int address, int value)
  189. -- Power On and set (initialize) the relevant addresses to the given values.
  190. function power_on()
  191.     emu.poweron()
  192.     memory.writebyte(COUNTER_ADDRESS,COUNTER_SET)
  193.     memory.writebyte(INPUT1_ADDRESS,INPUT1_SET)
  194.     memory.writebyte(INPUT2_ADDRESS,INPUT2_SET)
  195.     memory.writebyte(INPUT3_ADDRESS,INPUT3_SET)
  196.     memory.writebyte(RNG_ADDRESS,RNG_SET)
  197. end
  198.  
  199.  
  200. -- The FCEUX frame advance (advance the emulator one frame) command:
  201. -- emu.frameadvance()
  202. -- Power On and run the first inputs until the black screen, then the second inputs until door RNG.
  203. -- Since inputs perhaps may be delayed in frame advancing in FCEUX, the Power On occurs a few frames in.
  204. -- If the RNG result is successful, store and print the successful inputs.
  205. function run_test(input1,input2)
  206.     current = -2
  207.     while(current <= RNG_FRAME) do
  208.         if(current == 0) then
  209.             power_on()
  210.         end
  211.         if(current <= BLACK_FRAME) then
  212.             input_buttons(input1)
  213.         else
  214.             input_buttons(input2)
  215.         end
  216.         emu.frameadvance()
  217.         current = current + 1
  218.     end
  219.     if(compare_result(RESULT_ADDRESS,RESULT_SUCCESS)) then
  220.         report(input1,input2)
  221.     end
  222. end
  223.  
  224.  
  225. -- MAIN (Run Tests)
  226. -- On loading the script these instructions are automatically run utilizing the above variables and functions.
  227. -- Print in the console the initialized RAM settings.
  228. -- Test all the 5184 possible combinations of inputs, making each into a bit table during its test.
  229. -- Increment through each possibility filtering out the impossible 60352 combinations.
  230. -- Store and print in the console successes and notify when finished.
  231. INPUTS_SET = tostring(INPUT1_SET) .. " " .. tostring(INPUT2_SET) .. " " .. tostring(INPUT3_SET)
  232. emu.print("Counter Set: " .. tostring(COUNTER_SET) .. " || Inputs Set: " .. INPUTS_SET .. " || RNG Set: " .. tostring(RNG_SET))
  233. while(CURRENT_POWER < 256) do
  234.     temp1 = to_bit_table(CURRENT_POWER)
  235.     if(power_filter(temp1)) then
  236.         while(CURRENT_BLACK < 256) do
  237.             temp2 = to_bit_table(CURRENT_BLACK)
  238.             if(black_filter(temp2)) then
  239.                 run_test(temp1,temp2)
  240.             end
  241.             CURRENT_BLACK = CURRENT_BLACK + 1
  242.         end
  243.         CURRENT_BLACK = 0x00
  244.     end
  245.     CURRENT_POWER = CURRENT_POWER + 1
  246. end
  247. CURRENT_POWER = 0x00
  248. emu.print("DONE")
  249.  
  250.  
  251. -- ZeroNoin
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement