This Game Cheats? Hal Labs' LeMans for Commodore 64 (Part 3)

Sdílet
Vložit
  • čas přidán 31. 07. 2024
  • Finally we dig into the disassembled code of LeMans, with a focus on the time-keeping and scoring routines that are directly tied to this game's very flexible definition of a second. Can we conclusively show that this game cheats? Along the way we find a really neat trick for detecting multiples of 20,000 points, and other conventional and unconventional approaches to C64 game development. LeMans is one of the launch titles of the Commodore 64 and Commodore Max, released back in 1982, and it was developed by Hal Laboratory, possibly by Satoru Iwata himself.
    This series:
    Part 1: Dumping LeMans cartridge: • Dumping (And Playing) ...
    Part 2: Making the binary RUNnable: • Making a C64 Cartridge...
    Part 3: This Game Cheats: This video!
    BCD video: • Commodore 64 Game Prog...
    To support 8-Bit Show And Tell:
    Become a patron: / 8bitshowandtell
    One-time donation: paypal.me/8BitShowAndTell
    2nd channel: / @8-bitshowandtell247
    Index:
    0:00 Recap
    1:30 Since 8K is actually a lot of code...
    3:17 EE10: Incrementing the BCD score
    7:16 EE25: Checking for multiples of 20,000
    13:29 F39E: 1000 bonus points
    14:53 E182: Initializing time
    17:08 EFEE: Tracking time
    21:24 F462: Game over?
    25:20 F564: Definition of a second: the "cheat"
    28:58 E06D: CIA driven: 1.000000 MHz?
    32:50 E802: 6 unused bytes: DDE?
    35:14 Conclusion and thanks!
  • Věda a technologie

Komentáře • 97

  • @VulpisFoxfire
    @VulpisFoxfire Před 2 měsíci +22

    When life gives you Lemans...

    • @dominikschutz6300
      @dominikschutz6300 Před 2 měsíci +2

      ... you port it to other platforms. :)

    • @BillAnt
      @BillAnt Před 2 měsíci +3

      You make a Super-Snapshot dump lol

  • @frestkd
    @frestkd Před 2 měsíci +5

    When I was a kid I got the game I discovered that if stay on the left side of the track you can time it right and go for hours without crashing. Just modulate the throttle, and time the back and fourth cars.

    • @8_Bit
      @8_Bit  Před 2 měsíci +1

      You sure you're not thinking of Spy Hunter or some other similar game? I can't see how you could get through certain parts of LeMans like that, especially where the road splits into two narrow bridges that get clogged up with cars.

  • @whitslack
    @whitslack Před 2 měsíci +18

    Using a bitwise "AND" to compute the modulus by a power-of-2 divisor is a very common trick. In fact, that's how most (all?) optimizing compilers of higher-level languages like C implement unsigned modulus by a power-of-2 divisor.
    Edit: I see you addressed this in some other comments. I agree that it's a little surprising to see this trick used when the divisor is notionally 2000 (decimal), which is not a power of 2, but of course it is when it's stored in BCD format.

    • @8_Bit
      @8_Bit  Před 2 měsíci +7

      Yes, I've seen that many times, and used it in my own games etc. What I've never seen before is using it for detecting multiples of decimal numbers, like 20,000 / 40,000 / 60,000 etc. points.

  • @Jemacaza
    @Jemacaza Před 2 měsíci +19

    Always nice to dive into MOS 6510 assembly. Love it. Very nice video, thank you!

  • @NotIT
    @NotIT Před 2 měsíci +7

    Dummy 0's (zeros) was a very common practice with EM pinball machines as well.

  • @JustWasted3HoursHere
    @JustWasted3HoursHere Před 2 měsíci +11

    If the label said "Seconds" instead of "Time" it would be more flagrant. I think the better way to do it and still be considered "fair" would be if you got fewer and fewer extra seconds for completing a lap (instead of making those "seconds" shorter and shorter).

    • @8_Bit
      @8_Bit  Před 2 měsíci +9

      Yes, it's true that the game doesn't actually say "seconds", even in the manual. There's just 60 units of time that start off about second-length so I wrongly assumed they were seconds :)

    • @ZipplyZane
      @ZipplyZane Před 2 měsíci +1

      @@8_Bit I mean, even if they aren't exactly 1 second, it's bizarre that they change them.
      I'd test putting it at 3C for all of them, and see what you get.

  • @merman1974
    @merman1974 Před 2 měsíci +2

    That AND is clever, it basically discards a lot of values and concentrates on the point where the (decimal) numbers roll over to the next 20,000. Neatly programmed, and clever how it is checked twice (the standard score increment, and when the bonus 1000 is added). What really blows my mind is the discipline needed to switch between decimal mode and binary mode in the same section of code; dealing with different variables in different modes isn't easy - especially considering much of this would have been done without a modern assembler/development environment.
    The STX/STY definitely looks like leftover code. It may well be that it was originally written to preserve values, or the coder often added that sort of code at the start of an important subroutine. It's a good habit to store and fetch variables, unless your code is absolutely time-critical.
    It had not occurred to me that the game was NOT using screen timing/interrupts to determine the speed. Maybe that is down to being so early in the C64/Ultimax's life that such routines were not commonplace or indeed even suggested for use.
    Having played it a lot (in the joystick-patched version) I noticed that it gets difficult fast. And that "difficulty table" is a fairly efficient way of doing that.

  • @Commodore128Mode3
    @Commodore128Mode3 Před 2 měsíci +4

    Another amazing video Robin! You are tenacious! (I mean that in a very good way!) I wouldn't have the patience for delving into something like this. One more reason that I love this channel!

  • @pb7379-j2k
    @pb7379-j2k Před 2 měsíci +8

    I will say that if the title was the actual name of the city, "Le Mans" then it would be right to silence the "s". But since they made up a brand new word, LEMANS, then anything goes!

  • @Azuris190
    @Azuris190 Před 2 měsíci +2

    One of my favourite games :D I was between 2-6 when i played with the C64 and this game was just accsessible to such a small kid.
    Later on when i was 15 i played that game agains a friend, in one round i got really lucky and played about 15 Minutes straight. You can imagine that the Highscore was absurdly high... Was the last round we played haha

  • @kumrogames
    @kumrogames Před 2 měsíci +1

    Great video as always! Its neat to see how the inner workings of some of these games were coded.

  • @steven-vn9ui
    @steven-vn9ui Před 2 měsíci +2

    Fascinating, I only follow bits of what you say but it is interesting to see how things were done back then especially to save a few bytes

  • @DavidYoud
    @DavidYoud Před 3 měsíci +11

    Your reversing videos are my favorite videos. It's always fun to explore clever things the ancient ones did (like the AND mask compare). If we find their solutions clever, likely they did too. :)

    • @BillAnt
      @BillAnt Před 2 měsíci

      There were clever programmers even some 40 years ago. ;)

  • @mikegarland4500
    @mikegarland4500 Před 2 měsíci

    Great vid as usual. I always find these fascinating. I am still being amazed at all the little tricks for checking for different conditions. The 'and 1F' trick is definitely a clever one. Thanks for doing all the hard work you do for us.

  • @TheHighlander71
    @TheHighlander71 Před 2 měsíci +4

    The AND #$1F is not an obvious solution. Very clever. I was thinking...you want to check that the least significant digit is zero, so the F in $1F makes sure of that. The other check is just to see whether or not the number is even or odd. So checking for a $1 makes sure you have an even number. It makes sense but I would have probably not thought of it. Great stuff.

    • @BillAnt
      @BillAnt Před 2 měsíci +1

      That was a nice logical explanation.

    • @bluerizlagirl
      @bluerizlagirl Před 2 měsíci +1

      The 6502 also has an instruction called BIT, which performs a logic AND between the accumulator and the contents of some location in memory (it can take a long address anywhere in memory or a short one in ZP, but not an immediate operand) *without* saving the result, except for setting the Z flag if every bit of the result was 0. If you have the value you want to AND with in memory somewhere, you can just specify its address in a BIT instruction. Although it has to take an extra clock cycle to read the operand, the accumulator remains unaltered; so you don't need to store and retrieve it, which _might_ offer a net time saving.
      For instance, if you need to check if or not the high nybble of the accumulator is zero, you can BIT with some address containing &F0 (which is the opcode for a BEQ instruction, so is very likely already to be present in your code).
      BIT also sets the N flag according to bit 7 of the operand byte, and the V flag according to bit 6 of the operand byte. This can be useful for reading status flags without altering the accumulator contents. The opcode for RTS is &60, so a BIT instruction against an RTS (which is even more likely to be present) will set V. It's still 3 bytes and 4 cycles, but might be useful if you ever need to return _two_ bits of state information from a subroutine.
      One more use for BIT is to "mask out" a one or two byte instruction, by prefixing it with the opcode for BIT short (=&24) or long (=&2C) respectively and conditionally branching to the byte _after_ this opcode. If the branch is not taken, the instruction is interpreted as BIT; which will read from memory and alter the flags, but not affect the contents of the registers. Otherwise, what should have been the address for the BIT instruction will be interpreted as an instruction. For instance, 2C A5 01 looks like "BIT &01A5"; but a branch to the second byte will end up loading the accumulator with 1.

  • @lindoran
    @lindoran Před 2 měsíci +2

    Love this kind of deep dive

  • @ScottHiland
    @ScottHiland Před 2 měsíci +4

    Thanks, Robin! Good stuff. Bitwise ops still mystify me on first glance, I have to put my brain into a lower gear every time.

  • @tYNS
    @tYNS Před 2 měsíci +2

    Great Video Robin!

  • @HelloKittyFanMan
    @HelloKittyFanMan Před 2 měsíci

    Cool series, thanks! Happy Battle of Puebla Day Cinco de Mayo/Five of May!

  • @einblock1840
    @einblock1840 Před 2 měsíci

    Very well explained!

  • @sark3153
    @sark3153 Před 2 měsíci +5

    This game reminds me of those electro mechanical 'digital derby' toys from the 80s

    • @csbruce
      @csbruce Před 2 měsíci +2

      Robin made a video about these "zero-bit games": czcams.com/video/NwQMBV7Td1s/video.html

  • @dominikschutz6300
    @dominikschutz6300 Před 2 měsíci +1

    The song at the end really rocks :)

  • @RudysRetroIntel
    @RudysRetroIntel Před 2 měsíci

    Wow! A lot of work, but you did it! Thanks for sharing

  • @ralphm786
    @ralphm786 Před 2 měsíci

    Awesome series of videos, Robin! I love Le Mans as it's the only racing game I know of that uses the paddles, which I think are sadly underutilised. If you ever feel like coding another game for the C64, please consider Sega's Turbo with paddle support. John Champeau of Champ Games did an amazing version for the Atari 2600 & I would love to see this on the C64. Cheers

  • @PaoloBergamo
    @PaoloBergamo Před 2 měsíci

    Awesome video! Got nothing to add but a “fair” progression would be -6 -6 -5 then either -5 or -4

  • @NeilRoy
    @NeilRoy Před 2 měsíci +1

    I have to agree, that AND with 1F is genius. It covers so many cases and is really fast and efficient. 👍
    Also, you're a lot of things, "simple minded" isn't one of them!!! 😎

  • @chrisdixon5241
    @chrisdixon5241 Před 2 měsíci +2

    Yay, first time I catch one of your videos on the same day it gets released!
    Given the RTS at $F48A, I suspect that the code block from $F462 could be some standard routine that the authors reuse across games, and are just using good practice to store registers upon entering a subroutine before changing them, and restoring them before exiting.
    The fact that they swapped them in the process is unfortunate! I wonder if this could be a way to fingerprint other titles from them?

  • @mondrus72
    @mondrus72 Před 2 měsíci

    Very interesting.
    In my ZX Spectrum game puzzle game BlockZ there is an extra timer byte at the end of the data for each level, so each level gives a different amount of time.
    There are always 160 "seconds" of time to complete each level but the timer byte defines how long a second is by using the following formula (timer_byte/50). So the higher the value of the timer byte is then the slower the timer will go down because each of the 160 "seconds" will be longer.
    The timer is handled within the interrupt routine on my PAL machine every 50th of a second. It checks to see if the timer byte is greater than 0 and decrements it by one if it is. If it is zero it checks if the number of "seconds" are greater than zero. If it is then the number of "seconds" is decremented and the timer byte is reset to correct value for the length of a "second" for that level. If the "seconds" are zero then time is up, a life is lost and so forth.

  • @luca6819
    @luca6819 Před 2 měsíci

    I KNEW IT! 😂 One of my first two games, with the cartridge of International Soccer. I also had the paddles, mine were darker in colour, similar to the breadbin case iirc, with a red switch

  • @kestergreen3844
    @kestergreen3844 Před 2 měsíci

    Great video. The explanations of the routines were excellent. How were you able to find and identify the different routines originally? I can understand assembly code but I'd like to get into disassembly to be able to find a small routine like you have. How much disassembly did you actually have to do to locate the score counter?

  • @csbruce
    @csbruce Před 2 měsíci +10

    1:55 Sections of the code look quite space-inefficient.
    3:26 If they use Decimal mode, they should disable it in their IRQ routine, since the Kernel IRQ doesn't.
    12:54 Similarly, a test against $01 detects every even value. The same thing can be done for every power of 2. AND #$1F will check if the value is evenly divisible by 32, which in BCD is $20.

  • @c128stuff
    @c128stuff Před 2 měsíci +2

    Another use for BCD is the time of day clocks in the CIA chips.
    Why would you use those instead of the clock kept by the system interupts? Simple, you do not have to put any work into keeping the clocks in the CIA chips running. You do have to set a time, ensure they are configured for either 50hz or 60hz line power (see codebase64 for how to do this properly) and start them.. after that they'll keep time for you, also when your interupt does not run for a while, or with a 'wrong' interval for some reason (say.. loading something from disk).

  • @Phreno_Xeno
    @Phreno_Xeno Před 2 měsíci +2

    I had a pirated copy of this game on tape turbo loader. I'd forgotten about it for over 30 years until I happened upon this video.

    • @Okurka.
      @Okurka. Před 2 měsíci +6

      You wouldn't steal a car.

    • @PlasticCogLiquid
      @PlasticCogLiquid Před 2 měsíci +2

      I had a pirate version on disk :D

    • @8_Bit
      @8_Bit  Před 2 měsíci +7

      I had (and have) the original cartridge *and* a pirated copy on disk :) I actually preferred the pirate copy because it had been hacked to use joystick instead of paddles and I found that a bit easier to control, especially when exiting the pits.

    • @PlasticCogLiquid
      @PlasticCogLiquid Před 2 měsíci +3

      @@8_Bit I think my whole collection was about half and half legit/pirate :D The cracked versions always had trainers so that was nice.

    • @MichaelPohoreski
      @MichaelPohoreski Před 2 měsíci +2

      @@Okurka.No but I would _copy_ a car! /s

  • @8-bitwallofdoom
    @8-bitwallofdoom Před 2 měsíci

    In the 4th video do you patch the code to use a random # within certain bounds such that each subsequent level is more difficult or occasionally easier, or is this it? Just kidding : ) But here is a serious question: how many of the original cartridges are based on the same code-base? I assume some of the post Ultimax CBM carts were as well? Is there a complete list online somewhere? Thank you as always. I learn something every time!

    • @8_Bit
      @8_Bit  Před 2 měsíci

      That would be kind of interesting to have some variation in the difficulty levels. When luck and skill combine at the right time, you could get some very high scores.
      I'm not sure if any of these HAL Laboratory Max/C64 launch titles share a code-base at all, beyond probably a few re-used routines that were probably copied and shared (and modified a bit) around the office (which might have been a single bedroom with 5 guys crammed in it, from what I've read!). Oh, or do you mean across platforms? Like, some of these C64/Max titles can be traced back to the VIC-20 and even PET in their 6502 form, and they were also ported to Z80 machines in some cases. So a game like Avenger (Space Invaders) may truly have a code-base that can be traced across several platforms.
      But I don't think (for example) LeMans and Avenger on the C64/Max have much code in common. Each game had a single main developer from the HAL team and they would share advice and code snippets (likely on paper!) but probably nothing more organized than that.

    • @8-bitwallofdoom
      @8-bitwallofdoom Před 2 měsíci

      @@8_Bit Looking forward to your adding another BCD nibble-pair of digits to account for the yet-to-be-achived higher scores and your mod to generate a random x'th of a second-second that maybe has a 1/16th probability of zonking you with an impossible 'kill screen' fate.

  • @sulrich70
    @sulrich70 Před 2 měsíci

    Pal vs ntsc timing was my first thought

  • @Clancydaenlightened
    @Clancydaenlightened Před 2 měsíci

    13:10 or just read the current score, do a ror and compare
    Though this way probably save cpu cycles
    Since you need to load the score value, every increment, then rotate and compare
    Could use the rotate instruction and use the cpu flags as a compare mask, get rid of the and or try to combine the instructions
    Set it up to ror, and branch on carry or branch on overflow

  • @gkwgeek4509
    @gkwgeek4509 Před 2 měsíci

    It would be interesting to know why they chose to go to 64(40 hex) making the second "difficulty" slightly easier. Were they trying to lull you into a false sense of security or was the initial value 64 and then later changed to 60 by someone trying to match the refresh rate?

  • @murderdoggg
    @murderdoggg Před 2 měsíci

    Did you fix it then play it again? How much better could they have made the game if there was no timer or game over?

    • @8_Bit
      @8_Bit  Před 2 měsíci

      I didn't try changing the value as I suspect I'd be able to play it "forever" if I made it easier. As it stands I've sometimes had 300,000 point games (maybe 15-20 minutes of gameplay, I'm not sure) so I'm not really sure the game would be better for it. It would probably need another goal or gameplay element if it didn't "cheat" like this.

  • @Jacob-dn6hg
    @Jacob-dn6hg Před 2 měsíci +3

    5:07 INC doesn't apply the BCD adjustment, so it can't be used here. If A holds $09, INC would go to $0A instead of $10, irrespective of the D flag.

    • @8_Bit
      @8_Bit  Před 2 měsíci +1

      Aha, yes, of course. Thanks. I was thinking of "just" using INC for the carry into locations $0B and $0C but you're right, the same problem would happen there.

    • @Jacob-dn6hg
      @Jacob-dn6hg Před 2 měsíci

      @@8_Bit I figured that's what you meant. I actually meant A as in the accumulator, but I forgot that instruction was introduced later and doesn't exist on the 6510. Sorry that was ambiguous.

  • @bluerizlagirl
    @bluerizlagirl Před 2 měsíci

    18:22 Is the SEC at &EFFE strictly necessary? The BCC at EFF7 did _not_ branch; and we have done nothing since then that would affect the carry flag, so this suggests C should still be set when we get here.
    I guess you might need an explicit SEC if something else did a JMP to this section of code with the carry in an unknown state, but this seems like a waste of a byte and two ticks of the CPU clock.

  • @DerekLippold
    @DerekLippold Před 2 měsíci

    Listening to you explain it is a bit like when that guy in The Matrix claims he can see what’s going on by watching the code lol😂 even understanding, it’s still a bit mystifying. I couldn’t recall any of this material as readily.

  • @anthonybarker3483
    @anthonybarker3483 Před 2 měsíci

    Very interesting, why don’t you program a “fair” version? I’d love to play that

  • @firstsurname9893
    @firstsurname9893 Před 2 měsíci +1

    Has anybody typed in the program from the end credits song?

    • @mikegarland4500
      @mikegarland4500 Před 2 měsíci +1

      Yes, i have. In a nasty turn of circumstances, this song is cut off at the end!! Good thing I had an intact version to draw from in the past. (j/k.. I'm sure it wasn't intentional on Robin's part.)

    • @mikegarland4500
      @mikegarland4500 Před 2 měsíci

      10 b=49152:c=0
      20 read a:if a > -1 then poke b+c,a:c=c+1:goto 20
      30 sys 49152
      40 data 169,147,32,210,255,169,0,141,32,208,141
      50 data 33,208,238,32,208,238,33,208,76,13,192,-1

  • @stuartmcconnachie
    @stuartmcconnachie Před 2 měsíci +1

    If it was in binary, and not BCD, how would you code…
    Every 256 points?
    Every 512 bytes?
    etc
    Basically it’s very easy to check for any multiple of a power of 2 simply by checking the n low order bits are zero. Perhaps easiest to comprehend if you remember you can multiply 2^n simply by shifting left n bits. Axiomatically that shift moves zeros into the low order bits.
    Very similar for BCD, where you can check for powers of 10 for example 10, 100, 1000 etc. But also you can check for double those values by masking one extra bit in the next nibble in order to check the next digit is even and not odd.

  • @weedmanwestvancouverbc9266
    @weedmanwestvancouverbc9266 Před 2 měsíci

    I think the timer speeds up to make the difficulty go up

  • @douro20
    @douro20 Před 28 dny

    I didn't know HAL wrote games for Commodore. A couple of the games remind me of Activision titles, especially Slalom...

    • @8_Bit
      @8_Bit  Před 28 dny

      Yes, HAL got their start with Commodore. As hobbyists on the Commodore PET, then professionally on the VIC-20, Commodore Max, and Commodore 64. Then they got into MSX and of course Nintendo.

  • @fnjesusfreak
    @fnjesusfreak Před 2 měsíci

    I've actually done a fair bit of stuff involving using AND to perform a modulo.

    • @8_Bit
      @8_Bit  Před 2 měsíci +3

      Yeah, quite a few times I've covered using AND as a modulo on a binary number, such as AND #%00000111 to do a MOD 8 (remainder 0 to 7). What's new to me here is to use AND as a way of identifying decimal (Binary Coded Decimal) multiples, such as 2000, 4000, 6000 etc.

  • @granitepenguin
    @granitepenguin Před 3 měsíci +4

    the 20,40 compare is sneaky; it's not just you. Subtle mask compares can do some really weird things.

  • @tubeincompetence
    @tubeincompetence Před 2 měsíci +1

    Something something "S in Lemans" / comment for youtube algo :)

  • @HelloKittyFanMan
    @HelloKittyFanMan Před 2 měsíci

    "Read-only memory memory..." Oops, heh.

  • @ml.2770
    @ml.2770 Před 2 měsíci

    oooh, your score is sooo big.

  • @OscarSommerbo
    @OscarSommerbo Před 2 měsíci +2

    It isn't really cheating. It is game design 😅

    • @ShinSeikiEvan
      @ShinSeikiEvan Před 2 měsíci +1

      Did the original Sega arcade game have the same "cheating" mechanic, I wonder?

    • @OscarSommerbo
      @OscarSommerbo Před 2 měsíci +2

      @@ShinSeikiEvan Given the arcade machines of the era, I wouldn't be surprised.

    • @DaveF.
      @DaveF. Před 2 měsíci

      Ooo - I dunno - putting more cars on the track isn't cheating - redefining what a second is - that's a bit iffy 🙂

  • @klocugh12
    @klocugh12 Před 2 měsíci

    It doesn't cheat. It just accelerates itself to relativistic speeds so it experiences time dilation. From its perspective time passes perfectly on point.
    /s

  • @rager1969
    @rager1969 Před 2 měsíci

    You are definitely not simple minded, at least as it pertains to assembly ;)
    In all seriousness, you are clever so if you are amazed that says something.

  • @archivis
    @archivis Před 2 měsíci

    :))

  • @rossdtool
    @rossdtool Před měsícem

    At least you don’t pronounce it as’lemons’.

  • @Thiesi
    @Thiesi Před 2 měsíci +2

    0:03 - The "S" shouldn't be pronounced in "Le Mans". Now I feel better.

    • @jamesrowden303
      @jamesrowden303 Před 2 měsíci

      Wait until you mispronounce Grand Prix in mixed company, your day will get interesting

  • @faenethlorhalien
    @faenethlorhalien Před 2 měsíci +1

    It's pronounced with an "s" in most languages, as in "in most sensible languages", so don't worry at all.

  • @Okurka.
    @Okurka. Před 2 měsíci +1

    It's pronounced like 'lemons' but with an 'a'.

  • @yadabub
    @yadabub Před 2 měsíci +2

    Arcade games did need to "kill you off," because of coin detected in pocket.

    • @8_Bit
      @8_Bit  Před 2 měsíci +2

      Yes, and that didn't need to carry over to home video game consoles and computer games, but it did. That was my point.