Main > Driving & Racing Cabinets
Mame hacks that make driving games play better with mouse/spinner/360wheel
Yolo_Swaggins:
Hi geecab i managed to combine your code with my code so that my mods only get applied if airborne is loaded up in mame but all the others will use your code. I'm wondering if i should maybe add street drivin' to it because it kinda had a similar issue but not as bad. I notice if i only shift the output left by 7 bits instead of 8 the steering in airborne becomes more like the original games and you can get away with using a port_minmax of 0x000, 0x7ff with a center point of 0x400 or 0x400, 0xbff with a center point of 0x800. It applied EMA smoothing to the wheel output so that it doesn't fluctuate too wildly when turning the wheel and seems to make the wheel stay in alignment when driving for long periods but crashing and holding the wheel left or right or staying offroad until the timer runs out with the wheel held left or right will knock the centerpoint out of place even if you have the wheel only turned a little bit.
--- Code: --- // Merge in the wheel edge latch bit
if (m_hdc68k_wheel_edge)
{
result ^= 0x4000;
printf("hdc68k_port1_r: merge latch result=%04X m_hdc68k_last_wheel=%04X\n", result, m_hdc68k_last_wheel);
m_hdc68k_wheel_edge = 0;
}
m_hdc68k_last_port1 = result;
return result;
}
uint16_t harddriv_state::hda68k_port1_r()
{
uint16_t result = m_a80000->read();
if (m_hdc68k_wheel_edge)
{
result ^= 0x4000;
printf("hda68k_port1_r: merge latch result=%04X m_hdc68k_last_wheel=%04X\n", result, m_hdc68k_last_wheel);
m_hdc68k_wheel_edge = 0;
}
return result;
}
uint16_t harddriv_state::hdc68k_wheel_r()
{
static float ema_wheel = 0.0f; // Persistent storage of EMA across calls
static const float alpha = 0.05f; // Smoothing factor for EMA, adjustable
static bool initialized = false; // To check if ema_wheel is initialized
uint16_t new_wheel = m_12badc[0].read_safe(0xffff);
// Display the wheel position when left shift is pressed
if (machine().input().code_pressed(KEYCODE_LSHIFT))
{
popmessage("wheel new=%04X", new_wheel);
}
// Determine if the current game is Hard Drivin's Airborne
bool is_hdrivair = (machine().system().name == std::string("hdrivair"));
// Separate edge detection logic for hdrivair and non-hdrivair games
if (is_hdrivair)
{
// Use a specific mask for Hard Drivin's Airborne (placeholder for future changes)
if ((m_hdc68k_last_wheel & 0x0c00) != (new_wheel & 0x0c00))
{
m_hdc68k_wheel_edge = m_hdc68k_wheel_edge == 0 ? 1 : 0;
}
}
else
{
// Use the default mask for other games
if ((m_hdc68k_last_wheel & 0x0c00) != (new_wheel & 0x0c00))
{
m_hdc68k_wheel_edge = m_hdc68k_wheel_edge == 0 ? 1 : 0;
}
}
if (is_hdrivair)
{
// EMA calculation only for Hard Drivin's Airborne
if (!initialized)
{
ema_wheel = new_wheel; // Initialize EMA with the first reading
initialized = true;
}
else
{
ema_wheel = alpha * new_wheel + (1 - alpha) * ema_wheel; // Update EMA
}
}
else
{
// For other games, use the raw wheel data without EMA
ema_wheel = new_wheel;
}
m_hdc68k_last_wheel = new_wheel;
uint16_t output;
if (is_hdrivair)
{
// Hard Drivin's Airborne specific processing
output = (static_cast<uint16_t>(ema_wheel) << 7) & 0xFFF0; // Shift and mask for hdrivair
}
else
{
// For other games, shift left by 8 and set the last hex digit to 0xFF
output = (static_cast<uint16_t>(ema_wheel) << 8) | 0xFF;
}
// Return the processed wheel value
return output;
}
--- End code ---
geecab:
Hi Yolo!
I reckon you've discovered something that I've been all around the houses investigating today lol! I'll cut to the chase, Street Drivin's default steering values are CNTSPTRN: 512 PATCEN: -98. I think this is pretty much what you've discovered when bit shifting by 7 and not 8.
I made a python script that can read and compare the nvram files (That get created when you exit the game for the first time, and get modified when you perform wheel calibration) and reports interesting values (Currently only steering information is fished out, but it could be modified to gas pedal etc I guess). The script works fine for hard drivin, race drivin and street drivin. The airborne nvrams look like a different format and I don't think the CNTSPTRN stuff is saved at the same location. If it is the same location, then the result for airborne is CNTSPTRN: 255 PATCEN: 0.
I have added an option to edit/save the CNTSPTRN and PATCEN values in the nvram files but unfortunately the game won't accept it. I think the file is protected in some way.
I'm not sure if you are familiar with python, but here's my script that does the read/diff/reset if you're interested...
--- Code: ---import sys
BYTES_EXPECTED = 2048
def load_byte_array(filename):
global BYTES_EXPECTED
with open(filename, mode='rb') as file: # b is important -> binary
fileContent = file.read().hex()
data = []
byte_count = 0
this_byte = ""
for c in fileContent:
this_byte += c
if len(this_byte) == 2:
data.append(int(this_byte, 16))
this_byte = ""
byte_count += 1
if byte_count != BYTES_EXPECTED:
print("Error! Unexpected number of bytes in {}. Expecting={}, actual={} ".format(filename, BYTES_EXPECTED, byte_count))
exit(1)
#print("{}".format((fileContent)))
return data
def save_byte_array(filename, byte_array):
# make file
newFile = open(filename, "wb")
# write to file
newFileByteArray = bytearray(byte_array)
newFile.write(newFileByteArray)
def load_nvram(nvram_path):
global BYTES_EXPECTED
mainpcb_200e = load_byte_array(nvram_path + "mainpcb_200e")
mainpcb_210e = load_byte_array(nvram_path + "mainpcb_210e")
# Combine the data...
nvram = []
for i in range(0, BYTES_EXPECTED):
nvram.append((mainpcb_200e[i] << 8) | mainpcb_210e[i])
return nvram
def do_read(nvram_path):
nvram = load_nvram(nvram_path)
cntsptrn = "?"
patcen = "?"
for i in range(0, BYTES_EXPECTED):
temp = "ADDR:0x{:04X} - 0x{:04X} {:<8}".format(i, nvram[i], f"({nvram[i]})")
if i == 0x212:
temp = temp + " CNTSPTRN"
cntsptrn = nvram[i]
elif i == 0x213:
temp = temp + " PATCEN"
patcen = nvram[i]
print(temp)
i_patcen = patcen
if (patcen&0xf000) == 0xf000:
i_patcen = -1 * (0x1000 - patcen&0x0fff)
print(f"Read complete. CNTSPTRN:{hex(cntsptrn)} ({cntsptrn}) PATCEN:{hex(patcen)} ({i_patcen})")
def do_diff(nvram_path_a, nvram_path_b):
global BYTES_EXPECTED
a = load_nvram(nvram_path_a)
b = load_nvram(nvram_path_b)
diff_count = 0
for i in range(0, BYTES_EXPECTED):
if a[i] != b[i]:
temp = "ADDR:0x{:04X} - A:0x{:04X} {:<8} DIFF B:0x{:04X} {:<8}".format(i, a[i], f"({a[i]})", b[i], f"({b[i]})")
if i == 0x212:
temp = temp + " CNTSPTRN"
elif i == 0x213:
temp = temp + " PATCEN"
diff_count += 1
print(temp)
print(f"{diff_count} differences detected.")
def do_reset(nvram_path):
mainpcb_200e = load_byte_array(nvram_path + "mainpcb_200e")
mainpcb_210e = load_byte_array(nvram_path + "mainpcb_210e")
mainpcb_200e[530] = 0x04
mainpcb_210e[530] = 0x00
mainpcb_200e[531] = 0x00
mainpcb_210e[531] = 0x00
save_byte_array(nvram_path + "mainpcb_200e", mainpcb_200e)
save_byte_array(nvram_path + "mainpcb_210e", mainpcb_210e)
print("CNTSPTRN reset to 1024, PATCEN reset to 0.")
def clean_path(my_path):
if not my_path.endswith('/'):
my_path = my_path + '/'
return my_path
def show_usage():
print("Nvram Helper")
print("------------")
print("")
print("Usage:")
print(" python3 nvram_helper.py read <nvram_directory>")
print(" python3 nvram_helper.py diff <nvram_directory_a> <nvram_directory_b>")
print(" python3 nvram_helper.py reset <nvram_directory>")
print("Examples:")
print(" python3 nvram_helper.py read nvram/harddrivcb")
print(" python3 nvram_helper.py diff nvram/harddrivcb nvram/strtdriv")
print(" python3 nvram_helper.py read nvram/strtdriv")
exit(0)
#print("{}".format(sys.argv))
if len(sys.argv) < 2:
show_usage()
cmd = sys.argv[1]
if cmd == 'read':
if len(sys.argv) < 3:
print(f"Not enough arguments.")
exit(1)
do_read(clean_path(sys.argv[2]))
elif cmd == 'diff':
if len(sys.argv) < 4:
print(f"Not enough arguments.")
exit(1)
do_diff(clean_path(sys.argv[2]), clean_path(sys.argv[3]))
elif cmd == 'reset':
if len(sys.argv) < 3:
print(f"Not enough arguments.")
exit(1)
do_reset(clean_path(sys.argv[2]))
elif cmd.lower() in ["h", "-h", "help", "/help", "/h", "?", "--h"]:
show_usage()
else:
print(f"Unknown command '{cmd}'.")
show_usage()
--- End code ---
With the help of this script, I could work out the memory locations for when the game reads and writes CNTSPTRN and PATCEN stuff during execution time. This is kind of interesting because I can now automatically work out when to trigger the centre edge without having to rely on the user having to calibrate either wheel to the magic CNTSPTRN:1024 and PATCEN:0 numbers. I have actually got this working well, but my code is a bit complex/ugly at the moment. I'll post it soon.
BTW. I've had a few goes with airborne, sometimes (most of the time) I go backwards when I accelerate. I've had a look at previously posts, did you mention something about this to me? If not, have you seen this before?
:)
Yolo_Swaggins:
--- Quote from: geecab on April 28, 2024, 03:04:00 pm ---Hi Yolo!
I reckon you've discovered something that I've been all around the houses investigating today lol! I'll cut to the chase, Street Drivin's default steering values are CNTSPTRN: 512 PATCEN: -98. I think this is pretty much what you've discovered when bit shifting by 7 and not 8.
I made a python script that can read and compare the nvram files (That get created when you exit the game for the first time, and get modified when you perform wheel calibration) and reports interesting values (Currently only steering information is fished out, but it could be modified to gas pedal etc I guess). The script works fine for hard drivin, race drivin and street drivin. The airborne nvrams look like a different format and I don't think the CNTSPTRN stuff is saved at the same location. If it is the same location, then the result for airborne is CNTSPTRN: 255 PATCEN: 0.
I have added an option to edit/save the CNTSPTRN and PATCEN values in the nvram files but unfortunately the game won't accept it. I think the file is protected in some way.
I'm not sure if you are familiar with python, but here's my script that does the read/diff/reset if you're interested...
--- Code: ---import sys
BYTES_EXPECTED = 2048
def load_byte_array(filename):
global BYTES_EXPECTED
with open(filename, mode='rb') as file: # b is important -> binary
fileContent = file.read().hex()
data = []
byte_count = 0
this_byte = ""
for c in fileContent:
this_byte += c
if len(this_byte) == 2:
data.append(int(this_byte, 16))
this_byte = ""
byte_count += 1
if byte_count != BYTES_EXPECTED:
print("Error! Unexpected number of bytes in {}. Expecting={}, actual={} ".format(filename, BYTES_EXPECTED, byte_count))
exit(1)
#print("{}".format((fileContent)))
return data
def save_byte_array(filename, byte_array):
# make file
newFile = open(filename, "wb")
# write to file
newFileByteArray = bytearray(byte_array)
newFile.write(newFileByteArray)
def load_nvram(nvram_path):
global BYTES_EXPECTED
mainpcb_200e = load_byte_array(nvram_path + "mainpcb_200e")
mainpcb_210e = load_byte_array(nvram_path + "mainpcb_210e")
# Combine the data...
nvram = []
for i in range(0, BYTES_EXPECTED):
nvram.append((mainpcb_200e[i] << 8) | mainpcb_210e[i])
return nvram
def do_read(nvram_path):
nvram = load_nvram(nvram_path)
cntsptrn = "?"
patcen = "?"
for i in range(0, BYTES_EXPECTED):
temp = "ADDR:0x{:04X} - 0x{:04X} {:<8}".format(i, nvram[i], f"({nvram[i]})")
if i == 0x212:
temp = temp + " CNTSPTRN"
cntsptrn = nvram[i]
elif i == 0x213:
temp = temp + " PATCEN"
patcen = nvram[i]
print(temp)
i_patcen = patcen
if (patcen&0xf000) == 0xf000:
i_patcen = -1 * (0x1000 - patcen&0x0fff)
print(f"Read complete. CNTSPTRN:{hex(cntsptrn)} ({cntsptrn}) PATCEN:{hex(patcen)} ({i_patcen})")
def do_diff(nvram_path_a, nvram_path_b):
global BYTES_EXPECTED
a = load_nvram(nvram_path_a)
b = load_nvram(nvram_path_b)
diff_count = 0
for i in range(0, BYTES_EXPECTED):
if a[i] != b[i]:
temp = "ADDR:0x{:04X} - A:0x{:04X} {:<8} DIFF B:0x{:04X} {:<8}".format(i, a[i], f"({a[i]})", b[i], f"({b[i]})")
if i == 0x212:
temp = temp + " CNTSPTRN"
elif i == 0x213:
temp = temp + " PATCEN"
diff_count += 1
print(temp)
print(f"{diff_count} differences detected.")
def do_reset(nvram_path):
mainpcb_200e = load_byte_array(nvram_path + "mainpcb_200e")
mainpcb_210e = load_byte_array(nvram_path + "mainpcb_210e")
mainpcb_200e[530] = 0x04
mainpcb_210e[530] = 0x00
mainpcb_200e[531] = 0x00
mainpcb_210e[531] = 0x00
save_byte_array(nvram_path + "mainpcb_200e", mainpcb_200e)
save_byte_array(nvram_path + "mainpcb_210e", mainpcb_210e)
print("CNTSPTRN reset to 1024, PATCEN reset to 0.")
def clean_path(my_path):
if not my_path.endswith('/'):
my_path = my_path + '/'
return my_path
def show_usage():
print("Nvram Helper")
print("------------")
print("")
print("Usage:")
print(" python3 nvram_helper.py read <nvram_directory>")
print(" python3 nvram_helper.py diff <nvram_directory_a> <nvram_directory_b>")
print(" python3 nvram_helper.py reset <nvram_directory>")
print("Examples:")
print(" python3 nvram_helper.py read nvram/harddrivcb")
print(" python3 nvram_helper.py diff nvram/harddrivcb nvram/strtdriv")
print(" python3 nvram_helper.py read nvram/strtdriv")
exit(0)
#print("{}".format(sys.argv))
if len(sys.argv) < 2:
show_usage()
cmd = sys.argv[1]
if cmd == 'read':
if len(sys.argv) < 3:
print(f"Not enough arguments.")
exit(1)
do_read(clean_path(sys.argv[2]))
elif cmd == 'diff':
if len(sys.argv) < 4:
print(f"Not enough arguments.")
exit(1)
do_diff(clean_path(sys.argv[2]), clean_path(sys.argv[3]))
elif cmd == 'reset':
if len(sys.argv) < 3:
print(f"Not enough arguments.")
exit(1)
do_reset(clean_path(sys.argv[2]))
elif cmd.lower() in ["h", "-h", "help", "/help", "/h", "?", "--h"]:
show_usage()
else:
print(f"Unknown command '{cmd}'.")
show_usage()
--- End code ---
With the help of this script, I could work out the memory locations for when the game reads and writes CNTSPTRN and PATCEN stuff during execution time. This is kind of interesting because I can now automatically work out when to trigger the centre edge without having to rely on the user having to calibrate either wheel to the magic CNTSPTRN:1024 and PATCEN:0 numbers. I have actually got this working well, but my code is a bit complex/ugly at the moment. I'll post it soon.
BTW. I've had a few goes with airborne, sometimes (most of the time) I go backwards when I accelerate. I've had a look at previously posts, did you mention something about this to me? If not, have you seen this before?
:)
--- End quote ---
Hi geecab just logged in and read this now, thats really good work fishing out that info from the nvram i'll check out the script.
About the reverse thing in Airborne, theres a reverse button thats a toggle
PORT_BIT( 0x0200, IP_ACTIVE_LOW, IPT_BUTTON4 ) PORT_TOGGLE /* reverse */
You need to just set up a button for that and toggle it off or go into the input settings in game and go to the toggle section and turn it off there.
Had a long weird day but i'll have a mess around now and see if i can figure anything else out now.
Yolo_Swaggins:
Hi geecab so i've had some sleep and made my own python script that combines the nvram files like this "MAME\nvram\harddrivcb\combined_high_200e_low_210e". So i calibrated the wheel correctly and combined it into the file i just mentioned and saved it in another location. I then deleted the nvram files and started the game and calibrated the wheel wrong. i opened both the correct and incorrect files in HxD and compared the difference. Copied the bytes from the correctly calibrated and combined nvram file into the incorrectly calibrated one and saved it. Then i used another script to split the file back into the 2 original nvram files and loaded up the game. Works fine and the wheel was calibrated. What i noticed is at the very bottom of the combined file there is a couple of bytes that were changed and i copied them in from the correctly calibrated file and maybe thats why it works? I might have misunderstood what you were saying before was it only airborne you couldn't get the game to accept the values? As of now i've not even really looked at it i just wanted to see what was going on in the ones we know we can get working first.
OK ignore everything i said i just copied the bytes that changed in the middle part of the file above the high score name "phantom Photon" and it works fine even without the changes on the very last line in HxD being copied over.
Yolo_Swaggins:
That value you found before is the old brake max in Hard Drivin's Airborne so the wheels not at the same position.
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version