MFME extraction, etc #2
Replies: 44 comments 123 replies
|
Thanks for this, and apologies @cuavas, @james-wallace-ghub - I was thinking we need something for general MAME fruit machine discussion, this is perfect :) As an update - I am fully committed to the 'flakey but already working' approach on my MFME layout/config extractor again now (to much risk/work/complexity in the FML decoding approach). I spent last night doing the majority of the remaining Lamp component base info (thus far I've been just extracting enough to drive the Arcade Sim 'DX' machines, as it was all still in the experimental phase). Plan is:
I'll post the odd update as I pass key milestones, now we have a forum for this :) |
|
As you’ve probably noticed, I’ve been slowly converting layouts to use loops to generate and place the numeric lamp/button labels in the debug views. It saves thousands of lines in the layout files, and makes a checked out source tree a bit smaller. I have about 200 left to do for my current round. It’s fairly automated with vim scripting, but I still check the changes, so it’s boring work. As you guys are already no doubt aware, the reel components in layouts (inherited from AGEMAME) have been deprecated. They were problematic in multiple ways. They performed incredibly poorly (you could see the slowdown when the reels do their initial spin on start), they generate insane numbers of textures, and you’re limited to using fixed-size bitmap tiles. Being able to show a window on any element is more flexible (it can cater to things other than reels), and it performs a whole lot better. I migrated a couple of layouts to eliminate deprecated reel elements, but it’s going to be quite a while before they’re all done. There are some other people interested in helping out with it, but I’ve been putting off describing the process because I wanted to get the rest of the debug view cleanup done first. |
|
The Here’s an example of a new, relatively simple layout using scroll windows to implement reels: https://github.com/mamedev/mame/blob/445293464e4aa99320493f1a93d8caeff72a1a68/src/mame/layout/j_lc.lay This commit converts reel components for one layout: mamedev/mame@24a5f43 Essentially:
With the
|
|
@johnparker007 I saw you mentioned about your doing screen scraping/remote control of MFME. Just FYI that you do not need to do this with MAME (any more), @npwoods (Bletch) has a good example of how to remote control a MAME interface with Lua over at https://github.com/npwoods/bletchmame . Most (all?) MAME frontends have yet to catch up. |
|
@davidhaywood just to let you know I've finally got some free time again, so I've now finished up the first stage of scraping the components (used in the test MPU4 Adders & Ladders MFME layout). This gives me graphical assets along with a large human-readable json file containing a dump of all the extracted data: CLASSICClassicAddersAndLadders(Barcrest).zip So now I'll move onto implementing a new version of the MAME .lay xml builder. I might be able to salvage some stuff from the old one written in C, but I may just start from scratch, and do it cleaner... Once I've got it generating something I'll post another update with a MAME screenshot :) I may be doing some improvements, I'm considering putting a white border around clickable buttons, something like this mockup on the £1 button, so they are visually different to lamps: |
Extending the built-in components is the road to hell. It’s better to keep them simple. We’re not going to complicate them.
It’s one setting to change to turn on a plugin – you can get there from the main menu pretty easily. You’d be shocked how many “typical users” turn on the high score, autofire and input macro plugins straight away. I really don’t want to pander to users who can’t flip a setting in a menu.
Why are people so obsessed with using transforms to rotate things by 45° when the SVG code would be more readable using plain paths? I really don’t understand this.
You‘re not understanding what a group element actually does or how scrolling words.
But I think you’re trying to jam too much stuff into the internal layouts. Consider that in an external layout, you can supply SVG, PNG, JPEG or BMP files for reel graphics. If you want to de-duplicate the symbols in an external layout, you can just draw the same images in multiple elements (remember an element can contain multiple components). For internal layouts it‘s probably better to just use text for the reel symbols so people know what they are. |
|
@davidhaywood @james-wallace-ghub Hey guys :) I've been following the conversation you were having on mamedev/mame#10015 ... re: "the layout now specifies if the 7seg is inverted. I guess this makes sense, if they used different component types which inverted the signal or something. kinda feels like we're missing a level of abstraction somewhere, but as it's mostly guesswork based on behavior, it should be ok for now." I'm sure you already know, but just in case - in MFME, the emulator appears to be able to detect/model this without any additional game-specific configuration (i.e. within layout, as that also has a lot of per-game hardware settings, much like MAME's per-game drivers). Here's a screenshot showing the layout properties page for an LED on Andy's Big Time, plus the overall hardware config page... no option anywhere to manually invert 7 seg displays: Apologies if this is superfluous information :) It does suggest though that it's (somehow) possible to have these work at the emulation level, without a per game manual adjustment. Easier said than done of course :) But it does appear to be derivable. I have been continuing to work on the layout builder, another (very rough) WIP screenshot: |
|
fwiw, looking at the layouts we already have the one for 'Alphabet' is mostly correct, but it looks like that game doesn't use a standard '3 lamps per reel' setup. I'm guessing the layout generator assumed reel lamps were always a standard mapping? |
|
@davidhaywood I'm moving to a new layout with different reel symbols, so I can develop something for parsing reel symbols from text files/earlier versions of the .lay files. This was always going to be an issue - so was just wondering; how did you go about unscrambling the lamps themselves to make your test Classic Adders & Ladders MPU4 layout work correctly (once you'd given it the correct 8x lamp column Chr values)? Was it just a case of careful trial and error per lamp, or did you figure out any kind of magic way to determine the old lamp number -> new lamp number mappings? MPU4 Graffiti layout, showing incorrect lamps: 0x00, 0x60, 0x40, 0x60, 0x20, 0x40, 0x40, 0x20 (MAME driver correct lamp chr values) I'm guessing it was just working each lamp out manually, but thought I'd best check! :) - in which case, I guess I'd initially just generate them with these wrong lamps where they occur, perhaps someone can be tempted into working them all out in the source MFME layouts, so I can then rebuild them for MAME. |
|
well for Classic Adders and Ladders, yes, I went over the layout and manually fixed the lamp values. Basically each scramble value acts as a 'xor' on each row. I knew what the old layout expected, and was previously being provided, and I knew the difference in the values in my lamp values compared to the old ones. From there I created a table of what the 'old' lamp number in the layout was, vs. what it should be. I search+replaced each of the lamp values that would need changing, so "lamp64" was renamed "lampxxxx68" for example. After doing all the renames I then removed the 'xxxx' part. basically if you want to hand-fix the layouts to work you need to know the (incorrect) CHR lamp table values MFME used and the (correct) CHR lamp table MAME uses. I guess it would be cleaner if this could be built into the conversion process, but for Classic AL it was done by hand. |
|
Just checking in with another update :) The work is going slowly due to lack of time (the noisy neighbours on both sides of my terraced house have got the better of me, so now I'm balancing full time job + building false interior walls both sides of house to block the noise, so FME dev is getting a smaller slice of time than is ideal), but still progressing... Been doing work on more reel symbol layers, and also got JPM input/reel alignment working, here's a couple more test layouts: MPU4 Graffiti with fruit symbols/overlays: MPU4 Sunset Boulevard with boxed symbols: JPM Impact Big Bucks - some symbols have a red background stripe layer, buttons/coins/reels working (minor issue with hold 2/3 lamps to figure out): |
|
glad to see you're still chipping away, my progress has kinda stalled until the branch gets merged in, but I'm sure we'll get there in the end |
|
Adding a few zeroes to the end does nothing, adding a bunch of them prevents the layout from loading - pretty sure it's in the size. |
|
It doesn't look like its the size. |
|
I think this time I figured it out for some of the files: program mfme_decoder;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Classes, EasyCompression;
const
levelValue: array [0 .. 9] of TECLCompressionLevel = (eclNone, zlibFastest,
zlibNormal, zlibMax, bzipFastest, bzipNormal, bzipMax, ppmFastest,
ppmNormal, ppmMax);
var
fs: TECLFileStream;
inputFile, outputFile: string;
last4Bytes: Cardinal;
passwordInt: Integer;
const
XOR_CONST = $00C59A00;
{$R-}
{$Q-}
function mixer(a1, a2: Integer): Integer;
var
i: Integer;
x, v4: Cardinal;
addr: Cardinal;
begin
Result := a2;
addr := Cardinal(a1);
for i := 0 to 99 do
begin
x := addr xor Cardinal(Result);
v4 := (x shr 24) or (x shl 24) or (x and $00FFFF00);
Result := (v4 shl 3) or (v4 shr 29);
end;
end;
begin
try
if ParamCount < 1 then
begin
Writeln('Usage: mfme_decoder <inputfile>');
Exit;
end;
inputFile := ParamStr(1);
outputFile := ChangeFileExt(inputFile, '_decoded.dat');
var
fsIn := TFileStream.Create(inputFile, fmOpenRead or fmShareDenyNone);
try
fsIn.Seek(-4, soFromEnd);
fsIn.ReadBuffer(last4Bytes, 4);
finally
fsIn.Free;
end;
Writeln('Bytes: ', last4Bytes);
passwordInt := mixer(XOR_CONST, Integer(last4Bytes));
var
passwordStr := LowerCase(IntToHex(passwordInt));
Writeln('Password: ', passwordStr);
try
fs := TECLFileStream.Create(inputFile, fmOpenRead, passwordStr);
fs.SaveToFile(outputFile);
Writeln('File decoded to: ', outputFile);
finally
fs.Free;
end;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
Writeln('Trying alt password');
fs := TECLFileStream.Create(inputFile, fmOpenRead, 'b67de657');
fs.SaveToFile(outputFile);
Writeln('File decoded to: ', outputFile);
end;
end;
end.The last 4 bytes are the password but run through a XOR mixer 100 times. |
|
I created my own fml file and it seemed fairly structured and semi-meaningful but other fmls looked completely different but I eventually found some similarities. The images are stored as bitmap files with a control header before them. Easiest way to find them is look for the font name, "Tahoma" was used on the fml I looked at. Sometimes you get some extra 'tags' between the images, no idea what they do. Somewhere in all that is the number of "on" images as you can have images which aren't allocated to a lamp so aren't in the 12*4 table. That's all I've got |
|
For now I'm most interested in the element positions, this alone would make manual layout creation much easier. So parsing the fml to get parameters 01-04 is what I'm going for right now. |
|
Experimented a bit more with this and with a simple pattern search, with a bit of value validation, it's fairly easy to find the first resource block and then walk through the rest of the file. Question - for reels there is a height parameter which specifies how much of the reel band is displayed, does anyone know what units this is in ? It's not pixels or steps so I've no idea what it is. I've tried to get the library compiling in a c++ app but failed :( The demos work fine but they're gui. As soon as I try and include the lib in a console app it fails at link time with something like system.classes.obj :( |
|
I've had a quick look at how MFME draw lamps and I'm a little confused. For normal lamps you take the ON image and apply the mask so it looks like it's lit by a lamp then draw it on the background taking note of transparency and rotation etc. All fairly simple. I'm not sure what's supposed to happen when the BLEND box is ticked. There are some lamps with this ticked where the ON image contains the lit part but also contains unlit background. These images have no transparency. If you apply the mask the unlit background part of the image becomes too dark when drawn on the background. Does anyone know how this is supposed to work ? Similarly, laps with LED and RGB ticked also behave in a way I haven't yet figured out. |
|
Just wondering if it's possible to decode the fml files without using the easycompression library ? There appears to be some sort of header to the fml but apart from the obvious I'm stuck :( Edit: I was expecting the header to specify the length of compressed data, so file length-header length, and also the decompressed data length but I see neither :( Not really a problem, I can just call the decoder program first. |
|
Thanks for your reply. I originally looked into the fml format to help someone working on a project which needed a list of objects and their positions, sizes and rotation. That part, including saving the images was quite straight forward and I didn't progress it much further. I'd not worked out any of the tick boxes, dropdown menus or anything else. I picked it up again more recently as things seemed to have gone quiet on this thread and although I've made some progress I'm no where near to what you've achieved. As there's no point in re-inventing the wheel I think I'll drop the format investigation and see if I can get anywhere with the decryption side. My understanding was the library accepts a variable length string as a password and although mfme uses a 4 byte value converted to ascii I'm not so sure it's as straight forward as needing another 8 bytes. I've not done reversal of pc software before so might be a slow process. If I get anywhere I'll post on here. I look forward to seeing your decoder when released. |
|
@Paul-Arnold @johnparker007 @stonedDiscord Just to let you know. I've figured out how to decrypt and decompress without the library (sorry to take the task but by the time I had read your reply I had already invested loads of time.) I have C# code with tests, it works with every fml I have thrown at it so far. Figured out using Deepseek V4 Pro using OpenCode as a harness, took a few days to find the right approach. Bottom line is...It's now a solved problem and I will be integrating it into my codebase, just need a little time to do it. My C# implementation covers the use of a fallback password as well (as seen in the @stonedDiscord implementation) FML Decoder — How It WorksPipeline OverviewNo external binary, no emulation, no capstone. Dependencies: Step 1: Parse FML HeaderRead two 32-bit little-endian integers from fixed offsets:
The first 144 bytes (0–143) are the header. The payload starts at offset 144. Step 2: Derive Password via MixerThe last 4 bytes of the FML file encode the password. The Mixer AlgorithmPseudocode: Where Verified Passwords
Step 3: Derive AES Key — RIPEMD-128The password is used as raw 8-byte ASCII input to standard RIPEMD-128. RIPEMD-128 AlgorithmRIPEMD-128 is a 128-bit cryptographic hash function. It takes an arbitrary-length
Compression Function Constants
f-Functions
Round Operation (per round j)Both left and right lines execute the same pattern with different tables: Each round feeds Feed-Forward (Final Combination)After all 64 left and 64 right rounds, the new hash state is: Verified Key Outputs
Step 4: Derive IVThe AES key encrypts a block of 16 Step 5: Custom Decryption (ECL Mode)The ciphertext from offset 144 (length StateFull Blocks (16-byte aligned)For each 16-byte ciphertext block: This is not standard CBC. Key difference: CBC XORs the previous ciphertext Partial Block (if csize % 16 != 0)For trailing bytes (<16), encrypt Important: Uses Step 6: DecompressThe decrypted output is a raw zlib stream (deflate, no header wrapper for The output is a Delphi binary format (DFM/resource stream) with a consistent File Layout SummaryFML File Structure
DAT Output FormatDecompressed output is a Delphi binary stream with a |
|
Amazing work, brilliant. I'll try and understand what you've found later but I've a couple of really dumb questions :) Using cashcrusader.fml (£100 Dx version), file is 0X96D56e = 9885038 bytes So, the compressed length is 266464 and decrypts to 524288 ? but using the released mfme decoder the file decompresses to 29578180 I'm obviously being really stupid :) |
|
Here is the fun bit you have been waiting for :) It's reasonably clean but remember it is vibe coded and I have not given it a super fine review. Usage |
|
Any chance we cab get @cuavas to comment on common components for fruit games? I appreciate the old reels etc were slow, but it would be really useful to have some proper common component support for lamps, reels, alpha's etc. Internally at the emulation level and externally at the artwork level. |
|
Hello — hoping to tap the MFME knowledge in this thread, apologies as it's a tangent from layouts. I've been writing a Bell-Fruit Scorpion 5 hardware emulation for my own machines. Personal project, nothing distributed, and not an attempt at a rival to MFME. The board side works: MCF5206E ColdFire, YMZ280B, the alpha display, the SEC meters, the I2C devices. The CPU is verified instruction by instruction against a reference trace for 400 million instructions, and Deal or No Deal: The Golden Game boots properly — initialises, reads the sound ROM header, puts its startup text up. Then it stops with The exchange it wants is on I2C address 0x09, every boot:
Those 16 bytes are identical whatever the device answers (tried 00, ff, 5a), so it's a fixed challenge — meaning a single captured reply would satisfy it permanently, rather than needing the algorithm. Two questions if anyone knows:
I notice sc5 in MAME is still "this system is not emulated", so I assume this is the reason. |
|
Not sure why you're seeing it as fixed but it's definitely not a fixed challenge. |




















Uh oh!
There was an error while loading. Please reload this page.
A continuation of mamedev/mame#9632 (comment) , so we don't pollute the PR system any more.
All reactions