[HN Gopher] Modder re-creates Game Boy Advance games using the a...
       ___________________________________________________________________
        
       Modder re-creates Game Boy Advance games using the audio from crash
       sounds
        
       Author : dagenix
       Score  : 551 points
       Date   : 2024-01-22 17:39 UTC (1 days ago)
        
 (HTM) web link (arstechnica.com)
 (TXT) w3m dump (arstechnica.com)
        
       | 2024throwaway wrote:
       | This is insanely impressive. I'm sure many of the techniques used
       | like the "majority vote" algorithm cited are underutilized across
       | many industries.
        
         | epcoa wrote:
         | If one is interested there is nearly 100 years now of
         | sophisticated noisy signal recovery techniques.
         | 
         | Magnetic storage media operates on the principal of this hack
         | at baseline! https://en.wikipedia.org/wiki/Partial-
         | response_maximum-likel...
         | 
         | The same idea can apply here as GBA ROM material would be
         | highly biased. Majority vote wastes a lot of information.
        
           | rob74 wrote:
           | The problem with this signal is that it was never designed to
           | be recovered in the first place (it only came about by
           | chance), that's why it has the issue with long sequences of 0
           | bytes described in the article.
        
         | mikepurvis wrote:
         | I wondered about it for film scanning, specifically with a fan
         | project like 4K77 where they're dealing with potentially
         | damaged theatre prints rather than pristine masters-- having
         | multiple of them and being able to use that to eliminate
         | scratches and so on would potentially save a ton of time on
         | manual fixing in post.
        
         | guyomes wrote:
         | One interesting application of taking the value appearing in
         | majority, or more generally taking the median, is to remove
         | noise or people from a sequence of several photos taken from
         | the same point of view. This idea is to superimpose all the
         | photos, and for each pixel, you simply keep the median [1].
         | 
         | [1]: https://patdavid.net/2013/05/noise-removal-in-photos-with-
         | me...
        
           | astrange wrote:
           | This will have the effect of smoothing the image even if you
           | didn't want to do that, which is part of the reason people
           | are convinced phone cameras "overprocess" images or try to
           | beautify you even when they aren't.
           | 
           | You have to counteract this by actually putting the noise
           | back when you're done.
        
         | jacquesm wrote:
         | It's fairly common in data recovery though, just keep on
         | reading disk images and running quora until something passes
         | the checksum and see if it works out. Better still if there are
         | higher level checksums (for instance across a file that has a
         | signature) for further verification. Also used in some
         | aerospace applications.
        
       | brokensegue wrote:
       | Zzazz (the subject of the article) runs a yearly April fools
       | competition/event that usually involves some amount of retro
       | hacking/reverse engineering. I recommend participating. Previous
       | years competitions are on their GitHub
        
       | reactordev wrote:
       | It's stories like these that remind me I'm stupid and have a lot
       | still to learn. My goodness this is amazing. Up there with audio
       | key logging and things I read about but could never dream would
       | be a real thing.
       | 
       | I tip my hat sir. Reminded that I'm a mere mortal in the presence
       | of greatness. The absurdity of recreating a game based on audio
       | crashes sounds like something a mental patient would say but no,
       | here we are in 2024.
        
         | sunnybeetroot wrote:
         | You're not stupid because you don't know how to reverse
         | engineer a GBA game, unless you work for Nintendo and it's part
         | of your job description.
        
       | da768 wrote:
       | I imagine the 0xFF might be converted to 0x00 due to DC blocking
       | capacitors/high-pass filtering, audio circuits aren't really
       | suited for non-audible content.
       | 
       | He might get better capture accuracy with a digital oscilloscope
       | hooked directly at the chip's audio outputs if he's lucky, but
       | it's still pretty impressive he got a bootable image out of that.
        
         | Frenchgeek wrote:
         | From what I remember reading in the comments of his youtube
         | video, the point of the exercise was to do it with as basic
         | equipment as possible.Hence the hours of multiples passes to
         | average the error out instead of a proper data-logging
         | oscilloscope.
        
         | arcticbull wrote:
         | > I imagine the 0xFF might be converted to 0x00 due to DC
         | blocking capacitors/high-pass filtering, audio circuits aren't
         | really suited for non-audible content.
         | 
         | Yeah, you want to generate a signal with no DC bias, something
         | as simple as Manchester encoding will go a long way. If that's
         | not good enough, there's NRZ or even a convolutional encoding.
         | You also want to make sure you either send a sin wave, or if
         | you can't do that, at least make sure your square wave
         | frequency is high enough that it doesn't get eaten by the AC
         | coupling capacitors.
        
           | freeqaz wrote:
           | Would it be possible to get a higher quality read from using
           | something like an Arduino's I/O pins and some bit-banged C
           | code? I'd be curious to see what would be possible using
           | cheap, off-the-shelf tools since a lot of people don't
           | necessarily have an oscilloscope laying around. :P
        
             | fodkodrasz wrote:
             | Without looking up datasheets, just form the top of my
             | head: the Arduino DAC most likely has 12 bits resolution
             | (as common for cheap uCs), and maybe even slower sampling
             | than a soundcard. A sound card was probably better than
             | that even in the 1990s (say a Sound Blaster).
        
               | aidenn0 wrote:
               | The original Sound Blaster could only record at 8-bit
               | resolution at up to 12 kHz. The 2.0 could record at up to
               | 15kHz, still 8-bit.
               | 
               | The second generation of Sound Blaster was the first that
               | could record at 44kHz (mono) sampling rate, but was still
               | only 8-bits of resolution.
               | 
               | It wasn't until the 3rd generation Sound Blaster 16 that
               | 16-bit audio could be recorded.
        
               | fodkodrasz wrote:
               | Hmm, I had (false) memories of better capabilities.
               | Though my first soundcard was an SB Pro clone, later an
               | SB16, both almost capable :)
        
       | skeaker wrote:
       | Glad to see this getting more attention here, it was posted a few
       | days ago but drowned
       | (https://news.ycombinator.com/item?id=39037104). The original
       | video has a lot not mentioned in this brief article, including a
       | custom adapter that the hacker had to cut together manually to
       | get the right audio quality out of the DS.
        
         | taftster wrote:
         | Link to "original video":
         | 
         | https://www.youtube.com/watch?v=0-7PSmYYHF0
        
           | hifikuno wrote:
           | The video was great. I think my favourite part is where he
           | dumps the Chinese knock off version and finds the random ARM
           | code and reverse engineers it. So much cool stuff in there.
        
             | taftster wrote:
             | Right? This kind of stuff just makes me look silly in what
             | I'm able to achieve. I can only accomplish watching a video
             | of some guy doing bad ass stuff and maybe holding onto a
             | few notional details. People are just super smart
             | sometimes.
        
               | runnr_az wrote:
               | It's especially fun watching super smart people work on
               | somewhat ridiculous projects.
        
       | peddling-brink wrote:
       | Why would this happen in the first place? Is it common for these
       | games to dump state to audio? Is it a deliberate debugging tool
       | for the game devs?
        
         | 0xC0ncord wrote:
         | The author's previous video[1] explains the technical details
         | regarding this behavior.
         | 
         | Basically the way GBA sound works is there is a buffer in RAM
         | that the audio is streamed from, and an interrupt is supposed
         | to signal the hardware to begin reading data from the beginning
         | of the buffer again. However, if the interrupt is never fired
         | (such as when the game crashes), the audio stream will go
         | beyond the buffer and read other parts of memory.
         | 
         | [1] https://www.youtube.com/watch?v=wSWNkpqjtQY&t=361s
        
           | jboy55 wrote:
           | Would this be dependent on the audio file that was being
           | played during the crash to start at address 0 of the ROM? It
           | seems like it'd be highly unlikely you'd be able to get 100%
           | of the ROM.
           | 
           | Now if this was a hack where the thought was, "What if we
           | dumped the whole ROM to the audio buffer, could we recover
           | the complete ROM through audio analysis?"
        
             | ordu wrote:
             | I think hardware starts from 0 after reaching 0xfff..ffff
             | address. The article mentions that you need to wait before
             | starting to record, I assume it is a pause needed for
             | hardware to overflow address.
        
             | lelandbatey wrote:
             | That video seems to imply that the audio hardware
             | automatically wraps back to address 0 when it reaches the
             | end of RAM. That may not be true, but it's implied by the
             | animation in that video. And since they were apparently
             | able to dump the entire ROM via audio, I suppose there's
             | _some_ way to get the entire memory contents.
        
             | shadowgovt wrote:
             | The ROM on the GBA is mapped into memory at high memory
             | addresses (0x08000000 and above). The audio "working"
             | buffer is in low memory (I think somewhere near
             | 0x02000000?). An interrupt fires when the audio chip reads
             | to the end of the working buffer that looks something like
             | this:
             | 
             | - run the function to fetch the next batch of audio to
             | audio working RAM
             | 
             | - reset the audio read pointer to the beginning of audio
             | working RAM
             | 
             | When interrupts are disabled (because the game has
             | crashed), that "reset pointer" code never runs and the
             | audio circuit keeps reading way past the end of its buffer,
             | incrementing forever. Eventually it would increment into
             | the 0x08000000 range in which case the sounds it's emitting
             | map directly to the bits in the ROM.
        
             | aprilnya wrote:
             | the sounds aren't read directly from the rom - the buffer
             | is in a fixed place in ram, and sounds from the rom get
             | copied into there
             | 
             | remember that it's not like the music is just there as raw
             | audio, the raw audio is just the sounds of the different
             | instruments
        
               | jboy55 wrote:
               | That makes more sense, I thought they would just be
               | pointers to ROM addresses for the samples.
        
               | shadowgovt wrote:
               | No, they're read directly from the ROM.
               | 
               | Here's what's going on:
               | 
               | - reading from ROM is slow, relative to working RAM; the
               | ROM is mapped to the same memory address space as the
               | RAM, but reading a ROM address takes (I think 8?) clock
               | ticks.
               | 
               | - so to do audio playback, the GBA "stripes" a chunk of
               | data into RAM, sets a pointer to the beginning of that
               | stripe, and then lets the audio chip pull data and update
               | that pointer. When the pointer reaches the end of the
               | stripe, an interrupt triggers that is _supposed_ to pull
               | in the next chunk of audio into the stripe (overwriting
               | the current stripe contents) and reset the pointer.
               | 
               | ... but if interrupts are disabled, then that doesn 't
               | happen, and the simpler logic in the audio chip just
               | keeps incrementing the pointer forever and reading more
               | data. It'll eventually get to the ROM addresses and pull
               | directly from those (it's slow, but fast enough that it
               | doesn't starve the chip; you just wouldn't run your audio
               | this way in the game normally because you'd have 1/8th
               | the amount of time to do _everything else_ every frame of
               | animation if the audio system were reading directly from
               | ROM all the time. Also, reading from RAM lets you edit
               | the samples to do audio effects).
        
             | Dwedit wrote:
             | GBA Memory Map:                 02000000-0203FFFF   EWRAM
             | (256 KBytes)       03000000-03007FFF   IWRAM (32 KBytes)
             | 06000000-06017FFF   VRAM - Video RAM (96 KBytes)
             | 08000000-09FFFFFF   Game Cartridge ROM (max 32MB)
             | 
             | The audio buffer will usually live in EWRAM, then you have
             | to wait until about 100,000,000 audio samples have played
             | before it proceeds from EWRAM to Game Cartridge ROM.
        
         | shadowgovt wrote:
         | It's pretty uncommon and it's not a deliberate debugging tool.
         | In this case, the GBA could, hypothetically, have had a way to
         | "park" the architecture on a game crash, or "watchdog" the
         | system (by tying state update somewhere that should run
         | periodically to a non-maskable interrupt that reboots the
         | machine if that state update stops happening).
         | 
         | Simply because it costs more to do those things, the GBA
         | doesn't (Nintendo instead opting for the time-worn approach of
         | the great game cart manufacturers of old, "if our games don't
         | have bugs we don't have to worry about the behavior of the
         | hardware in undefined state!"). So when a GBA game gets into
         | some crash states (infinite loop with interrupts disabled, for
         | example), the audio chip doesn't know the system is crashed and
         | keeps doing its very simple job: reading sequential bits in RAM
         | and converting them to sounds. Without the housekeeping that
         | normally runs when the game is in good working order
         | shepherding that read operation, it just keeps reading and
         | eventually gets to the bits representing values in the
         | cartridge ROM.
        
           | Dwedit wrote:
           | The GBA does have an interrupt that occurs when cartridge is
           | removed.
        
             | krallja wrote:
             | WOW how did I never know about this?
             | 
             | https://gbadev.net/gbadoc/interrupts.html
             | 
             | > It is possible to switch cartridges and have the routine
             | resume execution on a completely different ROM.
             | 
             | That would have been a heck of a game mechanic back in the
             | early 2000s! Maybe I never saw it because game developers
             | were tired of the "please insert disk 2" of the Playstation
             | era. But it feels like a gimmick a Metal Gear Solid game
             | could have used.
        
       | thewakalix wrote:
       | The issue with long runs of 0x00 is related to "clock recovery".
       | 
       | > Some digital data streams, especially high-speed serial data
       | streams (such as the raw stream of data from the magnetic head of
       | a disk drive and serial communication networks such as Ethernet)
       | are sent without an accompanying clock signal. The receiver
       | generates a clock from an approximate frequency reference, and
       | then phase-aligns the clock to the transitions in the data stream
       | with a phase-locked loop (PLL).
       | 
       | > In order for this scheme to work, a data stream must transition
       | frequently enough to correct for any drift in the PLL's
       | oscillator. The limit for how long a clock-recovery unit can
       | operate without a transition is known as its maximum consecutive
       | identical digits (CID) specification.
       | 
       | https://en.wikipedia.org/wiki/Clock_recovery
        
         | akira2501 wrote:
         | I love how we used to use a bunch of very clever "code book"
         | systems like 8b/10b which did a lot of careful work with small
         | runs of bits to ensure the clock was recoverable and to avoid
         | line capacitance issues.
         | 
         | Then we just moved to things like 64/66b which takes a giant
         | chunk of bits, adds a short header to guarantee a clock
         | transition, then runs everything through a pseudorandom
         | scrambler.
        
           | dwattttt wrote:
           | A sprinkle of entropy improves everything
        
             | jacquesm wrote:
             | They do a similar thing for GPS data recovery. It is so far
             | below the noise floor that normally speaking the signal is
             | not recoverable. But then you inject some (known) noise and
             | suddenly the noise modulated by the (unknown) signal starts
             | to drown out the noise in the rest of the system and that
             | in turn allows you to recover bits from the signal.
        
               | akira2501 wrote:
               | It's not only below the noise floor, but all satellites
               | transmit the code on the same frequencies, so it's
               | several signals all at once below the noise floor. Which
               | makes the known noise unique to each satellite, and you
               | dredge out the same frequency repeatedly with different
               | sets of known noise to recover multiple signals.
               | 
               | Gold sequences are really neat, which is precisely the
               | same pseudorandom scrambling technique, but where each
               | sequence is selected to have low correlation with all
               | other sequences in use, which is what enables the
               | frequency sharing property of the system.
        
               | farhanhubble wrote:
               | This thread brings back fond memories from electronics
               | and digital signal processing.
        
               | rollcat wrote:
               | https://ciechanow.ski/gps/
        
               | jacquesm wrote:
               | That's a great article, you should post it separately.
        
               | rollcat wrote:
               | https://news.ycombinator.com/from?site=ciechanow.ski ;)
        
             | physicles wrote:
             | QR codes do this too, as explained by yesterday's front
             | page post about decoding QR codes by hand.
        
           | userbinator wrote:
           | Against adversaries, scrambling schemes can produce some very
           | perplexing behaviour. See the "weak sectors" used for CD copy
           | protection for one infamous example.
        
           | tux3 wrote:
           | The PCIe standards kept moving to longer codes, and nowadays
           | they're able to do "1b/1b" (no header at all)
        
             | phire wrote:
             | Not really accurate. The switch from NRZ to PAM4 actually
             | massively increased the bit error rate. They switched away
             | from the 8b/10b style line code and replaced it with
             | forwards error correction.
             | 
             | PCIe 6.0 uses 256 Byte frames, with 242 Bytes of data, 8
             | Bytes or CRC and 3 Bytes of error correction.
             | 
             | So it actually has way more overhead than the older
             | versions and their 128b/130b line coding, It's just at a
             | slightly different layer.
        
         | mlyle wrote:
         | Another concern is if the waveform is AC coupled somewhere and
         | DC can't pass.
         | 
         | Even if you have a perfectly synchronized clock, all-or-
         | mostly-1's will be the same as all-or-mostly-0's in the long
         | run.
        
         | andai wrote:
         | From this page I found a very interesting article: Wireless Set
         | Number 10
         | 
         | https://en.wikipedia.org/wiki/Wireless_Set_Number_10
        
         | hun3 wrote:
         | This is probably irrelevant. The audio is analog (requires DAC
         | to decode into bitstream) and is transmitted with some fixed
         | frequency (44.1kHz or 48kHz), without any particular
         | synchronization.
        
           | dtgriscom wrote:
           | Nope. If you're trying to recover bits from a signal which is
           | either high or low, and the signal stays high for a long
           | time, unless your clocks are perfect (they won't be) you
           | won't be able to tell just how many bits were in that long
           | period of "high"s.
        
       | Y_Y wrote:
       | This reminds me of the original iPodLinux hack almost twenty
       | years ago where the fourth-gen firmware was dumped via the piezo
       | speaker -
       | https://web.archive.org/web/20140810083116/http://www.newsci...
       | 
       | Coincidentally one of the things this facilitated was playing GBA
       | games on the iPod, though I was happy enough just to play Doom a
       | with the click wheel and watch monochrome videos.
        
         | nathancahill wrote:
         | Good memories. I still have my iPod Classic, I upgraded the
         | battery and added 4 256GB MicroSD cards. I saw a mod recently
         | for adding Bluetooth but it involves swapping the rear case and
         | I don't think I'll go that far. There's something timeless
         | about the design (and about owning copies of music files).
        
           | KennyBlanken wrote:
           | Every music file I have in iTunes and on my iPhone is a DRM-
           | less MP3 or AAC file.
        
           | xyzzy_plugh wrote:
           | Funny to see this mentioned. I was recently out skiing and
           | was using my phone to listen to music, as I used to do with
           | my 3rd generation iPod. Moments ago I was lamenting to my
           | colleague that while my phone's battery rapidly plummeted in
           | the cold despite being insulated and close to my body (no way
           | even remotely approaching freezing temperatures) my iPod
           | never struggled to play tunes all day. In fact I never recall
           | any noticeable difference in battery performance in cold
           | temperatures despite daily use for years.
           | 
           | And yet my phone gets a bit chilly and gives up the ghost.
           | 
           | I use Bluetooth otherwise I'd have dug out the 3rd gen for
           | the next ski day.
        
             | wegfawefgawefg wrote:
             | Was it a difference in battery chemistry?
        
             | speedgoose wrote:
             | Sample size N=1 too but I remember warming up my iPod to
             | get more battery while my iPhone survives pretty well while
             | streaming my GPS coordinates when I go skiing.
        
           | ace2358 wrote:
           | Given the 'super compute in your pocket' thing we say...
           | super computers turned out to be terrible at playing music
           | files without stopping.
           | 
           | My iPhone will regularly forgot what I was listening to
           | moments ago after I press pause. Insane.
        
             | tomduncalf wrote:
             | Yeah this is so annoying! It especially seems to happen
             | with 3rd party music player apps.
        
               | ace2358 wrote:
               | Also apps and or websites pausing my music but play no
               | sound... ugh. I want an iPod.app to come along.
               | 
               | Tiny player does nice but the os level issues are getting
               | insane.
               | 
               | Also apps that have their data deleted (Apple tv infuse
               | app, play:sub iOS) because the os said so. I have plenty
               | of space but things get deleted. I want to FILL my 256
               | phone please.
        
               | mensetmanusman wrote:
               | I thought everyone enjoyed being forced to watch an Apple
               | Music advert when they just want to play a song stored on
               | device!
        
       | seabass-labrax wrote:
       | Does anyone know what's going on when the TheZZAZZGlitch's
       | emulator reports that the game tries to jump to an invalid
       | address? I'm not so familiar with the ARM7 processor used in the
       | GameBoy Advance, but I can't imagine how it would be possible to
       | construct a jump call with an invalid value. Additionally, what
       | would happen if one of TheZZAZZGlitch's incorrectly reconstructed
       | ROMs was run on a real GameBoy?
        
         | cosarara wrote:
         | > I can't imagine how it would be possible to construct a jump
         | call with an invalid value
         | 
         | You can use the bx instruction to jump to any address stored in
         | a register.
         | 
         | > Additionally, what would happen if one of TheZZAZZGlitch's
         | incorrectly reconstructed ROMs was run on a real GameBoy?
         | 
         | It would crash, and eventually start playing the ROM on the
         | speaker, the whole point of the video :)
        
           | seabass-labrax wrote:
           | Thanks for the explanation! So it sounds like the emulator is
           | detecting the error 'out of band' with the emulated execution
           | flow, and choosing to throw an error message rather than
           | letting the execution continue. If it didn't catch the error,
           | it sounds like it would glitch out in much the same way as
           | the real GameBoy would. Is that right?
        
             | immibis wrote:
             | That's right. This sort of thing is more likely to indicate
             | a discrepancy between the emulator and the real hardware,
             | than actual game behaviour, so emulator developers choose
             | to treat it like a crash. Some emulators have "continue
             | anyway" buttons and some don't.
        
         | ehaliewicz2 wrote:
         | It is likely that the emulator only emulates accessing valid
         | parts of the GBA's memory map, and throws that error if invalid
         | parts are accessed. As to what happens on real hardware.. who
         | knows? :)
        
       | Trekker666 wrote:
       | I've done this using GoldWave opening game files to extract their
       | audio because I like the menu song or something, but nothing this
       | sophisticated and cool.
        
       | jacquesm wrote:
       | That is extremely clever. The persistence alone is impressive but
       | to get it to the point where it works well enough that you can
       | actually extract the ROM contents with high fidelity is next
       | level.
        
       | maxlin wrote:
       | The diffing of the real-world cartridge was the most interesting
       | part. Requires a goofy amount of unnecessary knowledge to just
       | jump in to reversing that and figuring out what its meaning is
       | lol
        
       | DerCommodore wrote:
       | Crazy
        
       | scarecrw wrote:
       | This reminds me of an attempt made by RGMechEx to retrieve game
       | code for an Atari game where the code itself was used to generate
       | a visual effect similar to TV static on the screen [1].
       | 
       | [1] https://www.youtube.com/watch?v=5HSjJU562e8
        
       | Rapzid wrote:
       | The most impressive part of all this is, IMHO, how the code was
       | modified by the bootleggers to run the game off writable flash vs
       | ROM plus volatile save memory haha.
        
         | mmaniac wrote:
         | This is a very common technique on bootleg Game Boy and Game
         | Boy Advance games which bootleggers use to save a few cents on
         | batteries. And in truth, it's not too difficult to make such a
         | patch.
         | 
         | Bootleggers will write per-game patches which flush the save
         | contents to writeable flash depending on a small amount of
         | reverse engineering work to discover where in the code the game
         | saves. However, since all official GBA games always save data
         | using functions from the Nintendo SDK, it's fairly
         | straightforward to hook those functions and make a generic
         | patch for any GBA game to save on a batteryless bootleg
         | cartridge.
         | 
         | I have written a patcher to do this which you can find here.
         | https://github.com/metroid-maniac/gba-auto-batteryless-patch...
        
       | goodboyjojo wrote:
       | this is a good article. very interesting stuff
        
       | shadowgovt wrote:
       | I've always found Nintendo's audio architectures to be
       | fascinating.
       | 
       | The original NES had a sample generator that could do arbitrary
       | waveforms and could be driven in two modes: one mode is you gave
       | it a memory address and it'd start reading the bits in and treat
       | them as an incredibly simple waveform (1 means 'go up one value',
       | 0 means 'go down one value'... So to do a flat wave you'd do a
       | run of 10101010etc.).
       | 
       | But you could also direct-drive the chip by continuously feeding
       | it an "initial value" directly from the CPU, which was actually
       | faster than the audio driver would read the bits from RAM
       | itself... Problem is that doing so ate up all your CPU cycles, so
       | you could only do it if you weren't doing anything else.
       | 
       | There were some games that took advantage of that: Battletoads
       | had higher-fidelity drum-hits that they could play when action
       | was paused, and they used it for the title screen, the
       | extra-"crunchy" final hits on enemies (where all the action
       | paused for a moment for dramatic effect), and the memorable pause
       | music.
       | 
       | Here's a demo of the game switching between direct-drive and let-
       | chip-read-samples mode to demonstrate (credit to Retro Game Audio
       | for uploading the emulator-doctored video to show when the game
       | is in the direct-drive subroutine).
       | https://www.youtube.com/watch?v=JGT0FM3yh-w
        
       ___________________________________________________________________
       (page generated 2024-01-23 23:02 UTC)