Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - Dumping Korean "128합" 128-in-1 Cartridge (likely pirate? missing original label)

Reply to topic
Author Message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Dumping Korean "128합" 128-in-1 Cartridge (likely pirate? missing original label)
Post Posted: Sun Apr 05, 2020 9:59 pm
Reference: https://www.smspower.org/forums/17892-Korean128128In1CartridgeLikelyPirateMissin...

This is my current best-guess dump in power-on scrambled state. It appears there is a write to 0x0000 (I don't know whether that's actually doing anything), to 0x2000 (I believe it's a page number XOR mask), and then to 0x6000 when a page mapping is requested. These page mappings seem to affect subsequent ROM reads from 0x4000 onward. Initialization code in the cartridge writes 0x42 to 0x0000 and 0x1F to 0x2000.

Only the first 0x80 pages are uploaded here. After that there are 0x80 pages of all zero bytes, but I don't know whether that is actually stored in a ROM or just indicates I read past the end of it.

These dumps are not yet fully verified, nor is the mapper behavior. I have not yet gotten it working in an emulator — doing so may well reveal further interesting behavior.

I am not Korean-literate, so I'm also looking for more expert assistance in what to name this dump. "128 Total" is my current best guess based on looking up the menu title. I could see "128 Hab" or similar transliteration being better too.

I have not yet gone looking in the binary for the menus to see whether there is any information there on who made this, or when.

Likewise I have not yet unshelled this cartridge to see whether it has any internal identifying information, e.g. text on the PCB. Perhaps it also has an identifiable mapper chip? No idea yet, unfortunately. The plastic shell is very brittle and I would rather not further damage it. It has some sort of locking tab or bolt/post keeping it together and I'm not sure how to separate the upper & lower sections

(*EDIT* removed attachment "128 Total (KR) (Unverified).zip" wrong dump)
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Tue Apr 07, 2020 4:40 am
Apologies, that dump was incomplete! Some of the pages in the initial mapping of the 0x0000 … 0xBFFF region were missing from that dump, and it was missing some parts entirely. To tide you over until I can re-dump, I've attached the linear dump of 0x0000…0xBFFF followed by the page dump in unscrambled mode starting at page 0, with trailing zeroed pages omitted. This means this overall "dump" is a little more than 512 KBytes, and it repeats some data. I believe this should contain all the game data I was looking at before and testing (in extracted form, with a different MSX BIOS replacement) using mednafen previously, but I haven't had time to actually verify that (I was working with literally dozens of different dump attempts when I ran those extracted pages in the emulator, and it's possible some of the extractions contained pages somehow omitted here)

(*EDIT* removed attachment "128 Total (KR) (Unverified) [LINEAR+PAGES].zip"
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Tue Apr 07, 2020 7:29 pm
OK, I discovered at least part of the problem here: I'm seeing both slightly inconsistent reads (occasional bit-flips) and very inconsistent mapper register behavior (sometimes the register write is effective, but sometimes not!) using the Tengu GG Driver. Probably this will need to wait for the next weekend, but I will be redumping many times over to attempt to work around these issues.

Also, since I am not yet sure how the mapper works, exactly, it is still a bit of a guessing game.

edit: also, initial mapper state appears to be somewhat random! I guess this is not unlike some other mapper chips previously documented. However it makes determining the operating principles a little challenging :)
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Tue Apr 07, 2020 7:56 pm
OK, I was finally able to get the same data back for the first 48 KB (0x0000 … 0xBFFF) many times with cartridge removal + at least 30 seconds "cooling down" period + re-insertion in between, and no register writes during the dump.

I am uploading that part here. This is not the complete cartridge, but may give a useful view of the state at power on. Now to find all the writes! I suspect there might be multiple mapping registers here.

(*EDIT* Removed "128 Total (KR) (Unverified) [FIRST 48K].zip")
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Wed Apr 08, 2020 5:39 am
Last edited by bsittler on Wed Apr 08, 2020 6:41 pm; edited 4 times in total
Here's the hacked-together version of the dump I was able to extract games from. It's not verified yet, but I have been able to extract many usable pages of game data from it.

Also I believe my previous guesses about the mapper were entirely wrong, but I don't have great new guesses to replace them yet :P

I think it's not actually scrambled in the ROM, that was most likely a combination of uninitialized mapper registers and trying to treat it too much like Codemasters.

The menu is structured like an MSX ROM but executed as an SMS cartridge, and so its first few bytes do some very strange things. On original hardware with appropriate power-on default values for registers and RAM I suspect the original cartridge does the right thing, and it certainly works on the Analogue Mega SG. However on a Game Gear (which has different power-on defaults) hot-swapping from an EverDrive running the SG2GG palette set-up program (which also affects machine state) it resets repeatedly after drawing the first page of the menu.

Here is the shell command I used to extract the menu from the attached reconstructed dump — this repeats some data but that seems to match the layout the code expects, where e.g. address 0x4672 is clearly intended to contain the same instructions as address 0x0672; this also is a very MSX-ish layout, since on the MSX ROM cartridge data starts at 0x4000 by default:

(
  dd bs=16384 count=1 < "128 Total (KR) (Unverified) [RECONSTRUCTED].sms";
  dd bs=16384 count=2 < "128 Total (KR) (Unverified) [RECONSTRUCTED].sms"
) > "Menu (128 Total) (KR) [HACK].sms"


I have also attached a hacky patch I applied to the extracted menu "Menu (128 Total) (KR) [HACK].sms" to get it to run in emulators without resetting (very similarly to how the real cartridge resets on a Game Gear, maybe the cause is even related?)

(*EDIT* Removed " 128 Total (KR) (Unverified) [RECONSTRUCTED].zip")
Menu (128 Total) (KR) [HACK+MAPPER].ips (60 B)
Apply to: Menu (128 Total) (KR) [HACK].sms

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Wed Apr 08, 2020 6:41 am
Still haven't figured out how the unscrambling actually works at runtime, not even whether it's implemented in the mapper hardware or somehow in the menu/BIOS code, but here's a dump I've at least been able to extract repeatedly with no bit variations that contains all the game data. It was dumped using this Squirrel script on the Tengu GG dumper:

function cpu_dump(d, rom_size, banksize)
{
    // Korean 128-in-1

    local block_step = 0x2000;

    for(local a = 0x0000; a < rom_size; a += block_step)
    {
        local i = a / block_step;

        cpu_write (d, 0x8000, i);
        cpu_write (d, 0x8000, i);
        cpu_write (d, 0xa000, i);
        cpu_write (d, 0xa000, i);

        cpu_write (d, 0x4000, i);
        cpu_write (d, 0x4000, i);

        cpu_write (d, 0x6000, i);
        cpu_write (d, 0x6000, i);

        cpu_read (d, 0x6000, block_step);
    }
}


As you can see, I tried writing page numbers to lots of different places until I found a combination that worked. I haven't yet succeeded in producing a minimal write, though, which would be a lot more useful for emulator authors and documentation purposes.

The pages come out scrambled, with their page numbers XOR'ed with 0x1E.

I unscrambled them for further analysis like this with a shell script:

(
  i=0;
  while [ $i -lt 0x80 ]; do
    dd bs=$((0x2000)) skip=$(( $i ^ 0x1E )) count=1 < "128 Total (KR) (Unverified) [SCRAMBLED-XOR-1E].sms" ;
    i=$((1+$i));
  done
)  > "128 Total (KR) (Unverified) [RECONSTRUCTED].sms"


(*EDIT: Removed "128 Total (KR) (Unverified) [SCRAMBLED-XOR-1E].zip")
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Wed Apr 08, 2020 6:58 am
Finally, this is the hacky tool (shell command, really) I have used when comparing dumps:

(
  : these are the files to compare blocks from ;
  set \
    "128 Total (KR) (Unverified) [SCRAMBLED-XOR-1E].sms" \
    "128 Total (KR) (Unverified) [RECONSTRUCTED].sms";
  : this is the block size to hash ;
  k=$(( 0x2000 ));
  : this is the total number of bytes to consider ;
  sz=$(( 1024 * 1024 ))
  i=0;
  while [ $(( $i * $k )) -lt $sz ]; do
    printf %02X $i;
    printf ' ';
    echo $(
      for x in "$@"; do
        printf ' ';
        dd bs=$k count=1 skip=$i < "$x" 2>/dev/null | \
          shasum -a 1 |
          awk '{print $1}' |
          sed $'s/[1-6]/\e[3&m&\e[m/g';
      done
    );
    i=$(( 1 + $i ));
  done
)


It is horribly slow, unsurprisingly, but the output can be useful still.
Screen Shot 2020-04-07 at 23.57.49.png (119.34 KB)
Example output from block comparison script
Screen Shot 2020-04-07 at 23.57.49.png

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Wed Apr 08, 2020 4:49 pm
Initially mapped 8KB pages at power-on, in terms of their positions in both the scrambled and reconstructed dumps:

Cartridge slot address : scrambled page : reconstructed page
0x0000 : 0x1E : 0x00
0x2000 : 0x1E : 0x00
0x4000 : 0x7E : 0x60
0x6000 : 0x7F : 0x61
0x8000 : 0x7C : 0x62
0xA000 : 0x7D : 0x63

With this mapping you can reconstruct "128 Total (KR) (Unverified) [FIRST 48K].sms" (which is probably where emulator authors should begin investigations) from "128 Total (KR) (Unverified) [SCRAMBLED-XOR-1E].sms" or "128 Total (KR) (Unverified) [RECONSTRUCTED].sms"

edit: Indeed, the mapping for 0x4000… 0xBFFF is not consistent at power-on, but this one (it happens to be MSX Q*Bert a.k.a. Q*Bert's Qubes) seems to be the most frequent one.
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Wed Apr 08, 2020 7:18 pm
I am wondering whether the first few bytes of the cartridge (which happen to look like an MSX ROM header) also serve to initialize the mapper for the first paging area. This could mean the mapper effectively partly resets itself after a console reset (is that even a button found on Gam*Boy? I don't know!) with the aid of the CPU and the MSX ROM header bytes present on many pages.

First few bytes, hex dump view:
00000000: 4142 3446 001c bebe c362 0600 f37e c900


I believe the first eleven bytes disassemble to:


__handler_for_RST_0:
    LD B, C
    LD B, D
    INC (HL)
    LD B, (HL)
    NOP
    INC E
    CP (HL)
    CP (HL)
    JP 0x0662


So, does anyone know the power-on state of registers and memory on the Samsung Gam*Boy? And does the Gam*Boy have a reset button?

I believe the Game Gear resets registers to all ones (0xFFFF / 0xFF) but that older Sega consoles may reset registers to all zeros (0x0000 / 0x00) due to their differing CPU's, but I am not certain about that. And I have no idea what reads from RAM would return on these consoles right after power on or reset. Does anyone here know?
  View user's profile Send private message
  • Site Admin
  • Joined: 08 Jul 2001
  • Posts: 8652
  • Location: Paris, France
Reply with quote
Post Posted: Thu Apr 09, 2020 1:24 pm
Welcome to my life :)

In a few occasions in the past I ended up running statistics on unstable data. I think when I first dumped Panzer Dragon Mini (I think? maybe another game) on the GG something in the hw setup was flaky - mind you almost two decades ago, and I still know nothing about hw - I ended up doing thousands of reads over entire days and running per-bit statistics with bias on some bits lines. Ended up with a dump that we later confirmed as correct through other means. Probably have done that half a dozen of times since for whatever reason I couldn't figure how to solve otherwise.

Also lots of wiggling, unplugging, turning on/off, cleaning etc.
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 08 Jul 2001
  • Posts: 8652
  • Location: Paris, France
Reply with quote
Post Posted: Thu Apr 09, 2020 1:32 pm
Last edited by Bock on Fri Apr 10, 2020 3:22 pm; edited 1 time in total
- Most of memory tends to be $F0 on a Gam*Boy or Japanese SMS.

- No Reset button on either of them.

- Some games ported to MSX actually have roms that magically works on both systems, see:
https://www.smspower.org/Tags/MSXCompatibleROM
https://www.smspower.org/forums/13193

- I haven't followed your thread in details, so may be completely wrong but there is a non-zero possibility you could be overthinking or overengineering a solution. In my experience those software are rarely very "smart" they are mostly the result of products created hastily with poor tooling and mistakes made. So maybe considering that, you could look in another direction and find some evident simplicity to be found, hidden within that forest of apparent complexity.
  View user's profile Send private message Visit poster's website
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Fri Apr 10, 2020 2:50 am
Bock wrote
- Most of memory tends to be $F0 on a Gam*Boy or Japanese SMS.

- No Reset button on either of them.

- Some games ported to MSX actually have roms that magically works on both systems, see:
https://www.smspower.org/Tags/MSXCompatibleROM
https://www.smspower.org/forums/13193

- I haven't followed your thread in details, so may be completely wrong but there is a non-zero possibility you could be overthinking or overengineering a solution. In my experience those software are really very "smart" they are mostly the result of products created hastily with poor tooling and mistakes made. So maybe considering that, you could look in another direction and find some evident simplicity to be found, hidden within that forest of apparent complexity.


Thanks for the Gam*Boy info, and I agree it's probably a far simpler system than any of the guesses I wrote here. Hopefully further hardware interactions can fully explain it
  View user's profile Send private message
  • Joined: 14 Apr 2013
  • Posts: 624
Reply with quote
Post Posted: Fri Apr 10, 2020 10:14 am
It looks like [RECONSTRUCTED] is the correct dump.

The mapper register accessed by the menu is at $2000.
In the menu code the values written to there are XOR'd with $1f.
The first value written there is $1f. Which corresponds to page 0 when XOR'd with $1f.
This matches bsittler's finding that appending the first 32KB right after the first 16KB makes the menu show in emulators.

When using the mapper register at $2000 the mapper works as follows:
First 16KB ($0000-$3fff) are unpaged.
Second 32KB ($4000-$bfff) are paged as 32 KB pages on 8 KB boundaries. I.e. page 0 (write $1f to $2000) maps to $0000-$7fff, page 1 (write $1e to $2000) maps to $2000-$ffff, page 2 (write $1d to $2000) maps to $4000-$bfff, ...
This makes all menu entries start the games they should, and thus the dump fully playable.

Since bsittler has dumped the ROM via writes to $6000 and got the pages in a different order and in a different address range, it looks like the mapper is capable of more than just what the menu uses. We will further investigate this.
  View user's profile Send private message Visit poster's website
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Fri Apr 10, 2020 2:55 pm
Nice! However, doesn't that actually mean SCRAMBLED is the correct dump? In that case that mapper does not even XOR. And initial mapping is

0x0000: 0x1E
0x2000: 0x1E
0x4000: 0x7E
0x6000: 0x7F
0x8000: 0x7C
0xA000: 0x7D
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Fri Apr 10, 2020 2:59 pm
Never mind, see now... the addition only works in the version where the ROM is unscrambled, unless there are actually more writes to other addresses
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Fri Apr 10, 2020 6:49 pm
Way out of my price range, but I notice a lot of apparent overlap in terms of games with the menu listing on the back of the case in this listing:

https://www.ebay.com/itm/Sega-Samsung-Gamboy-Mark-III-Aladdin-188-Master-System-...
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Fri Apr 10, 2020 6:50 pm
oooh, except that one has some (likely MSX-originating) titles I haven't seen elsewhere, including Mr. Do!
  View user's profile Send private message
  • Joined: 01 Oct 2014
  • Posts: 27
  • Location: Sunnyvale, California
Reply with quote
Post Posted: Sat Apr 11, 2020 1:22 am
bsittler wrote
I am wondering whether the first few bytes of the cartridge (which happen to look like an MSX ROM header) also serve to initialize the mapper for the first paging area.

I don't think so, it looks more like a clever way to be able to boot the same ROM on both MSX and SMS.

If you run this as a cartridge on MSX, it will be mapped starting at 4000h, and the first bytes will be interpreted as a MSX ROM header :
4142   ID for a MSX ROM header
3446   INIT routine of cartridge is at 4634h, jump there

(we can ignore the 001c bebe bytes for now but I'll come back to it later. On a regular MSX cartridge they would be used to define 2 other entry points as you can see at the link above but here they are useless so they are used for another purpose)

Whereas on SMS, the ROM will be mapped starting at 0000h.
As you correctly showed the first 8 bytes
4142 3446 001c bebe
will be interpreted as (garbage) code than can be ignored, and then the important
c362 06
which jumps to 0662h.

Because of the difference in mapping address, this means we should have:
* The init routine for MSX at offset 0634h in the ROM
* The init routine for SMS at offset 0662h in the ROM

Now if we look at the disassembly in this area, we get:

0634:
    di
    im 1
    xor a           ;\ write 0 to RAM address F000 to signal we are
    ld (F000h), a   ;/ booting in MSX mode
    ld sp, F300h    ;  set stack just below system variables area
    call 0138h      ; BIOS call RSLREG Read primary SLot REGister
    rrca            ;\
    rrca            ;| find which MSX slot is mapped to page 1 (4000h)
    and 03h         ;/ i.e. which primary slot is this cartridge at
    ld c, a         ;\
    ld b, 00h       ;|
    ld hl, FCC1h    ;| look up slot expansion table, so that
    add hl, bc      ;| C = primary slot for cartridge
    or (hl)         ;|        + 80h if the slot is expanded
    ld c, a         ;/
    inc hl (x4)     ;\ read current value of mirror secondary slot selection
    ld a, (hl)      ;/ register for this cart's slot
    and 0Ch         ; only care about 2ndary slot for page 1 (4000-7FFF)
    or c            ;\
    ld h, 80h       ;| BIOS call ENASLT: also map page 2 (8000-BFFF)
    call 0024h      ;/ to cartridge's primary & secondary slots
    xor a           ;\  mapper init? map cart ROM bank 0 to page ??
    ld (6000h), a   ;/ of MSX address space??
    jp 4672h        ; jump below
0662:
    di
    im 1
    ld a, 01h       ;\ write 1 to RAM address F000 to signal we are
    ld (F000h), a   ;/ booting in SMS mode
    ld a, 1Fh       ;\  mapper init?
    ld (2000h), a   ;/
    ld sp, F300h    ;  set stack
0672:
    code continues here...


A few instructions below, we have:

0688:   call 0090h

On MSX, this is the address of the BIOS call GICINI (GI sound Chip INItialize; in other words, initialize the PSG)
On MSX hardware, this will call the actual BIOS (BIOS ROM is at 0000-3FFF, cart ROM is at 4000 and above)
On SMS, this will call 0090 in the cart ROM you dumped. If you look at the code there, this looks very much like MSX BIOS emulation code for SMS that you see in MSX -> SMS ports...
And so the meaning of the mysterious bytes 001c bebe is revealed.
In MSX ROM BIOS at this offset you find first the base address of the MSX character set in ROM; and 2 bytes for the base port addresses for VDP data read and write, which are indeed BEh on SMS.

Also is this the bank-switching code found by Calindro?
Note that it behaves differently depending on which hardware runs the code:

08CC:
    ld a, (F000h)   ;\
    or a            ;| if we booted in MSX mode write desired ROM bank
    jr z, 08DA      ;/ number as-is to mapper at address 6000h
    pop af
    xor 1Fh         ;\ else in SMS mode XOR it first
    ld (2000h), a   ;/ then send to mapper at address 2000h...
    jr 08DE
08DA:
    pop af
    ld (6000h), a
08DE:
    ...
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Mon Apr 13, 2020 1:23 am
Last edited by bsittler on Mon Apr 13, 2020 3:53 am; edited 2 times in total
Ok, that did the trick! Thank you all for your assistance, insight and ideas.

function cpu_dump(d, rom_size, banksize)
{
    // Korean 128-in-1

    local block_step = 0x2000;

    for(local a = 0x0000; a < rom_size; a += block_step)
    {
        local i = a / block_step;

        cpu_write(d, 0x2000, i ^ 0x1F);
        cpu_write(d, 0x2000, i ^ 0x1F);
        cpu_read(d, 0x4000, block_step);
    }
}


This gives back the same dump as [RECONSTRUCTED], but since I'm now confident this is the correct dump I'll shorten the name and re-upload. Here's the fingerprinting information for this dump:

1.0M '128 Total (KR).sms'
Checking for export header with matching CRC... NO
sha256:d398fff1599211711d037a073db02b131c7d00bbdd13e64abbd816545c3b4dee 128 Total (KR).sms
sha1:07d4be0635bab0a6523019c8ce2c23d9cd3b3e1d 128 Total (KR).sms
md5:6dddeb0bb21622b6ee1deb4c4870f801 128 Total (KR).sms
mekacrc:0707EB856DC38BC7 128 Total (KR).sms
crc32:ba5ec0e3 128 Total (KR).sms


Given the new, more-like-to be-correct XOR value (0x1F), it looks like this mapper's most common power-up state is equivalent to writing 0x7F to 0x2000, but it is indeed not 100% reliable.

This mapper is really quite simple, as several of you hypothesized. It can't even add! It has one register that I know of, I'll call that MPR, which holds a one-byte page number.

MPR is written any time there's a write to 0x2000 (this address is incompletely decoded, 0xA000 is an alias though 0x6000 is not).

Known register, not completely decoded:

0x2000: MPR, initial value usually 0x7F

Memory layout is as follows:

0x0000…0x1FFF: hard-wired to page 0x1F
0x2000…0x3FFF: hard-wired to page 0x1F
0x4000…0x5FFF: mapped to page (MPR ⊕ 0x1F)
0x6000…0x7FFF: mapped to page (MPR ⊕ 0x1E)
0x8000…0x9FFF: hard-wired to page (MPR ⊕ 0x1D)
0xA000…0xBFFF: hard-wired to page (MPR ⊕ 0x1C) * [see below]

⊕ indicates XOR

* Reads from 0xA000…0xBFFF don't seem completely reliable, the last four bytes from (unscrambled) pages 0x02, 0x06, 0x16, 0x1E, and 0x6F usually return different values inconsistently. As an example, when reading through the 0xA000…0xBFFF window, reading what should be the linear ROM addresses 0xDFFFC…0xDFFFF usually returns instead the bytes from linear ROM addresses 0xCFFFC…0xCFFFF.

(*EDIT* Removed "128 Total (KR).zip": correct dump, now published)
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Mon Apr 13, 2020 2:30 am
There's more: it's a bit unreliable for 0x0000…0x1FFF and also (similarly) for 0x2000…0x3FFF, both of which are unreliably hard-wired to logical page 0x00 (same page you get at 0x4000 when writing 0x1F to 0x2000).

Writing any value to 0x0000 and then delaying a fraction of a second (there's a delay loop in the ROM that seems to do this too) does indeed make the mapper stabilize more consistently for 0x0000…1FFF, but there's still about a 10% chance than one byte in those regions will have some bit errors. Reading the same linear ROM page 0x00 through the mapper window at 0x4000 seems to be 100% reliable though.

Clearly this is reliable enough that the game "usually" works. In fact so far I've never seen the bit-resets in regions containing menu code.
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Mon Apr 13, 2020 2:33 am
Last edited by bsittler on Sat Mar 13, 2021 6:25 am; edited 3 times in total
For posterity, the Tengu GG Dumper Squirrel script I have been iteratively hacking on while exploring the mapper, unredacted:

gg.nut (Korean 128-in-1 version — be sure to back up your original version and restore it afterward for regular Sega mapper cart dumping!)
function cpu_dump(d, rom_size, banksize)
{
    // Korean 128-in-1

    local block_step = 0x2000;
    local mapper_register = 0x2000; // also working: 0xA000
    local read_window = 0x4000; // also working: 0x6000, 0x8000; semi-working: 0xA000
    local page_bias = (read_window - 0x4000) / 0x2000;
    local stabilization_delay_seconds = 0.119;

    // menu initialization code does this, not sure whether it matters
    local reset_write_address = 0x0000;
    local reset_write_value = 0xF1;

    // HACK: this is an approximation based on my hardware+software
    // set-up, adjust for your computer / dumper speed since Tengu
    // does not expose Squirrel time() or clock()
    local fake_write_seconds = 0.030;
    local ram_write_address = 0xC000;
    local ram_write_value = 0x1F;

    for(local a = 0x0000; a < rom_size; a += block_step)
    {
        local i = a / block_step;

        cpu_write(d, reset_write_address, reset_write_value);
        cpu_write(d, reset_write_address, reset_write_value);
        cpu_write(d, reset_write_address, reset_write_value);

        cpu_write(d, mapper_register, 0x00 ^ 0x1F);
        cpu_write(d, mapper_register, 0x00 ^ 0x1F);
        cpu_write(d, mapper_register, 0x00 ^ 0x1F);

        for (local delay_completed = stabilization_delay_seconds, fake_elapsed = 0.0;
              fake_elapsed < delay_completed;
              fake_elapsed += fake_write_seconds) {
            cpu_write(d, ram_write_address, ram_write_value);
            cpu_write(d, ram_write_address, ram_write_value);
            cpu_write(d, ram_write_address, ram_write_value);
        }

        cpu_write(d, mapper_register, (i ^ page_bias) ^ 0x1F);
        cpu_write(d, mapper_register, (i ^ page_bias) ^ 0x1F);
        cpu_write(d, mapper_register, (i ^ page_bias) ^ 0x1F);

        for (local delay_completed = stabilization_delay_seconds, fake_elapsed = 0.0;
              fake_elapsed < delay_completed;
              fake_elapsed += fake_write_seconds) {
            cpu_write(d, ram_write_address, ram_write_value);
        }
        cpu_read(d, read_window, block_step);
    }
}
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Mon Apr 13, 2020 2:42 am
Tried some more, pretty sure the cart just becomes unreliable after being powered for a while. Unplugging it and letting it sit reduces the error rate measurably. I suspect it's just old, slightly-failing hardware that was possibly even never all that reliable to start with
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Mon Apr 13, 2020 4:03 am
Realized now I didn't check systematically for incomplete decoding of the lower bits of the mapper register's address. Anyhow, I checked it now and it's clear only the bits represented by the mask 0x6000 are checked. This means the mapper register has lots of aliases:

0x2000…0x3FFF, 0xA000…0xBFFF: all the same mapper register.

Also, the mapper register is 8 bits wide even though only 7 bits are needed for the 8 megabit/1 MB ROM. I wonder whether they made a "256합" 256-in-1 with 16 megabits/2 MB of ROM too?
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Mon Apr 13, 2020 5:27 am
Here's a patch that gets this one running in Meka

edit: fixed Krypton to Clapton [it's Clapton II]

diff --git a/meka/meka.nam b/meka/meka.nam
index 50d255a..a903cff 100644
--- a/meka/meka.nam
+++ b/meka/meka.nam
@@ -41,6 +41,8 @@
 ;  14 : sms 4 Pak All Action
 ;  15 : sg-1000 II with taiwanese memory expansion adapter, 8KB RAM from 0x2000->0x3FFF + regular 2KB RAM mapped at 0xC000-0xFFFF
 ;  16 : sms korean xx-in-1, register at 0xFFFF mapping 32 KB at 0x0000->0x8000
+;  17 : sc-3000 Survivors Multicart
+;  18 : sms korean 128-in-1 with 8KB page size, register @0x2000; 0x4000-0xBFFF: register^0x1[FEDC], 0x0000-0x3FFF: page 0
 ;-----------------------------------------------------------------------------
 
 ;-----------------------------------------------------------------------------
@@ -77,6 +79,7 @@
 ; DATA - SEGA MASTER SYSTEM (SMS)
 ;-----------------------------------------------------------------------------
 
+SMS ba5ec0e3 0707EB856DC38BC7   128 Total/COUNTRY=KR/EMU_MAPPER=18/COMMENT=Pirated MSX Games: Spelunker, Penguin Land, Tennis, Kung-Fu, Step UP, Picture Puzzle, Peetan, Mr. Chin, Lode Runner, Kung Fu II, Road Fighter, Sky Jaguar, Rally-X, Tank Battalion, Pyramid Warp, Hang-On, Demon Castle Legend, Pippols, Pooyan, Pac-Man, Heavy Boxing, Looping, Zaxxon, Goonies, Mopiranger, Fantasy Table Tennis, Mappy, Fruit Search, Galaxian, Zanac, Flicky, King's Valley, Magical Tree, Galaga, Eagle, Chick, Twin Bee, Castle, Circus, Hyper Rally, Dig Dug, Bomber Man, Dragon Attack, Star Force, Wonder Boy, Bread Factory, Cabbage, Bosconian, Q*Bert, King Balloon, Sports II, Trouble, Warp Warp, Antarctic Expedition, Buta Pants, Cannon Ball, and Clapton
 SMS f0f35c22 02079D9D9AF6B27B   20 em 1/COUNTRY=BR
 SMS 56dcb2d4 9FA930C396F93600   3D Gunner [Proto]/EMU_MAPPER=0/EMU_INPUTS=LIGHTPHASER/EMU_3D/EMU_LP_FUNC=2/COMMENT=Prototype version of the unreleased game.
 SMS a67f2a5c 4EB5F14E8488C9E9   4 PAK All Action/COUNTRY=AU/EMU_MAPPER=14
diff --git a/meka/srcs/machine.cpp b/meka/srcs/machine.cpp
index 43cd1f9..d61e4fe 100644
--- a/meka/srcs/machine.cpp
+++ b/meka/srcs/machine.cpp
@@ -182,6 +182,9 @@ void    Machine_Set_Handler_Write(void)
     case MAPPER_SMS_Korean_Xin1:
         WrZ80 = WrZ80_NoHook = Write_Mapper_SMS_Korean_Xin1;
         break;
+    case MAPPER_SMS_Korean_128in1:
+        WrZ80 = WrZ80_NoHook = Write_Mapper_SMS_Korean_128in1;
+        break;
     default:                             // Standard mapper
         WrZ80 = WrZ80_NoHook = Write_Default;
         break;
@@ -372,6 +375,20 @@ void    Machine_Set_Mapping (void)
         Out_SMS(0xE0, g_machine.mapper_regs[0]);
         break;
 
+    case MAPPER_SMS_Korean_128in1:
+        Map_8k_ROM(0, 0);
+        Map_8k_ROM(1, 0);
+        Map_8k_ROM(2, 0x60 & tsms.Pages_Mask_8k);
+        Map_8k_ROM(3, 0x61 & tsms.Pages_Mask_8k);
+        Map_8k_ROM(4, 0x62 & tsms.Pages_Mask_8k);
+        Map_8k_ROM(5, 0x63 & tsms.Pages_Mask_8k);
+        Map_8k_RAM(6, 0);
+        Map_8k_RAM(7, 0);
+        g_machine.mapper_regs_count = 1;
+        for (int i = 0; i != MAPPER_REGS_MAX; i++)
+            g_machine.mapper_regs[i] = 0;
+        break;
+
     default: // Other mappers
         Map_8k_ROM(0, 0);
         Map_8k_ROM(1, 1);
diff --git a/meka/srcs/mappers.cpp b/meka/srcs/mappers.cpp
index 3a2ec73..8689ff2 100644
--- a/meka/srcs/mappers.cpp
+++ b/meka/srcs/mappers.cpp
@@ -497,6 +497,35 @@ WRITE_FUNC (Write_Mapper_SMS_Korean_Xin1)
     Write_Error (Addr, Value);
 }
 
+WRITE_FUNC (Write_Mapper_SMS_Korean_128in1)
+{
+    if ((Addr & 0x6000) == 0x2000) // Configurable segment -----------------------------------------------
+    {
+        RAM[0x1FFF] = Value;
+        // This is technically incorrect: to mimic the actual hardware
+        // we would either need to use an overdumped 2MB ROM, or we
+        // would need to preserve all the segment base bits, as page
+        // numbers past the end of the ROM return zeroes in real
+        // hardware.
+        Value = ((Value ^ 0x1F) & tsms.Pages_Mask_8k) ^ 0x1F;
+        g_machine.mapper_regs[0] = Value;
+        Map_8k_ROM(2, g_machine.mapper_regs[0] ^ 0x1f);
+        Map_8k_ROM(3, g_machine.mapper_regs[0] ^ 0x1e);
+        Map_8k_ROM(4, g_machine.mapper_regs[0] ^ 0x1d);
+        Map_8k_ROM(5, g_machine.mapper_regs[0] ^ 0x1c);
+        return;
+    }
+
+    switch (Addr >> 13)
+    {
+        // RAM [0xC000] = [0xE000] ------------------------------------------------
+    case 6: Mem_Pages[6][Addr] = Value; return;
+    case 7: Mem_Pages[7][Addr] = Value; return;
+    }
+
+    Write_Error (Addr, Value);
+}
+
 // Based on MSX ASCII 8KB mapper? http://bifi.msxnet.org/msxnet/tech/megaroms.html#ascii8
 // - This mapper requires 4 registers to save bank switching state.
 //   However, all other mappers so far used only 3 registers, stored as 3 bytes.
diff --git a/meka/srcs/mappers.h b/meka/srcs/mappers.h
index cc0c709..2194a64 100644
--- a/meka/srcs/mappers.h
+++ b/meka/srcs/mappers.h
@@ -40,6 +40,7 @@
 #define MAPPER_SG1000_Taiwan_MSX_Adapter_TypeA  (15)        // 8KB RAM from 0x2000->0x3FFF + regular 2KB ram in 0xC000-0xFFFF range
 #define MAPPER_SMS_Korean_Xin1                  (16)        // Mapping register at 0xFFFF to map 32 KB at 0x0000->0x8000
 #define MAPPER_SC3000_Survivors_Multicart       (17)
+#define MAPPER_SMS_Korean_128in1                (18)        // 8KB obfuscated segment MPR @0x[23AB]xxx; <0x4000: block 0, 0x[45]xxx: block MPR^0x1F; 0x[67]xxx: block MPR^0x1E; 0x[89]xxx: block MPR^0x1D; 0x[AB]xxx: block MPR^0x1C
 
 #define READ_FUNC(_NAME)   u8 _NAME(register u16 Addr)
 #define WRITE_FUNC(_NAME)  void _NAME(register u16 Addr, register u8 Value)
@@ -76,6 +77,7 @@ WRITE_FUNC (Write_Mapper_SMS_Korean_Janggun);
 WRITE_FUNC (Write_Mapper_SMS_4PakAllAction);
 WRITE_FUNC (Write_Mapper_SG1000_Taiwan_MSX_Adapter_TypeA);
 WRITE_FUNC (Write_Mapper_SMS_Korean_Xin1);
+WRITE_FUNC (Write_Mapper_SMS_Korean_128in1);
 //-----------------------------------------------------------------------------
 void Out_SC3000_SurvivorsMulticarts_DataWrite(u8 v);
 
diff --git a/meka/srcs/saves.cpp b/meka/srcs/saves.cpp
index abf1455..330832b 100644
--- a/meka/srcs/saves.cpp
+++ b/meka/srcs/saves.cpp
@@ -104,6 +104,9 @@ void        Load_Game_Fixup(void)
         case MAPPER_SC3000_Survivors_Multicart:
             Out_SMS(0xE0, g_machine.mapper_regs[0]);
             break;
+        case MAPPER_SMS_Korean_128in1:
+            WrZ80_NoHook (0x2000, g_machine.mapper_regs[0]);
+            break;
         }
     }
 
@@ -290,6 +293,7 @@ int     Save_Game_MSV (FILE *f)
     case MAPPER_SMS_Korean_MSX_8KB:
     case MAPPER_SMS_Korean_Janggun:
     case MAPPER_SMS_Korean_Xin1:
+    case MAPPER_SMS_Korean_128in1:
     default:                 
         fwrite (RAM, 0x2000, 1, f); // Do not use g_driver->ram because of g_driver video mode change
         break;
@@ -460,6 +464,7 @@ int         Load_Game_MSV(FILE *f)
     case MAPPER_SMS_Korean_MSX_8KB:
     case MAPPER_SMS_Korean_Janggun:
     case MAPPER_SMS_Korean_Xin1:
+    case MAPPER_SMS_Korean_128in1:
     default:
         fread (RAM, 0x2000, 1, f); // Do not use g_driver->ram because of g_driver video mode change
         break;

128 Total (KR)-01.png (124.79 KB)
Meka showing the first menu screen
128 Total (KR)-01.png
128 Total (KR)-10.png (155.47 KB)
Meka showing Spelunker
128 Total (KR)-10.png
128 Total (KR)-43.png (135.68 KB)
Meka showing the Hang-On title screen
128 Total (KR)-43.png

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Tue Apr 14, 2020 9:36 pm
Last edited by bsittler on Thu Apr 23, 2020 4:38 am; edited 1 time in total
Having native emulator and flash cartridge support for this obscure mapper is great where it is possible, but there are many cases where it's likely impractical to add or realistically never will be supported.

For those cases I would like to add these two small patches. Both of them apply to 128 Total (KR).sms.

128 Total (KR) [HACK+MAPPER+PARTIAL].ips - this patch in IPS format leaves the ROM size at 1MB and gets some (but not all) of the games to work using the standard "Sega" mapper found in emulators and flash cartridges. The non-working games (those attempting to map an odd-numbered page at 0x4000) will simply reset back to the menu. 13 games and their variations are not working in this hack: Clapton, Buta Pants, Cannon Ball, Bomber Man, Dragon Attack, Eagle, Chick, Fruit Search, Galaxian, Heavy Boxing, Tank Battalion, Pyramid Warp, Picture Puzzle, Mr. Chin, and Warp Warp. The menu and the remaining 41 games and their variations should work fine, though I have not tested them exhaustively.

128 Total (KR) [HACK+MAPPER+2MB].bps - this patch in BPS format doubles the ROM size to 2MB which breaks compatibility with some emulators and flash cartridges, but works, e.g., in builds of Meka that have not been patched to support the custom mapper used by this multicart and gets all of the games to work using the standard "Sega" mapper.

The BPS file is inside a ZIP file since this forum does not allow BPS files to be uploaded as attachments.

edit: The "128 Total (KR) [HACK+MAPPER+2MB]" version appears to function perfectly on the EverDrive GG X7, though the default TMS mode palette there leaves something to be desired (at least it has one, though!)

edit 2: It turns out only the first 1344KB (1376256 bytes = 10.5 megabits, a little less than 1.4 MB) of that ROM are actually needed (less than that, actually, but that's the smallest multiple of 64 KB that fits everything), and shortening it to that length improves emulator compatibility. The new patch is likewise in BPS format, and called "128 Total (KR) [HACK+MAPPER+1344KB].bps". It is likewise ZIP'ped due to forum attachment file extension rules

edit 3: The patches are replaced. The new versions leave the MSX header intact.

edit 4: The patches are replaced again. The new versions adapt the MSX code path to the ASCII8/ASC8 MSX MegaROM mapper (with all games functional) for 128 Total (KR) [HACK+MAPPER+PARTIAL].ips or to the ASCII16/ASC16 MSX MegaROM mapper (again, with all games functional) for 128 Total (KR) [HACK+MAPPER+2MB]and 128 Total (KR) [HACK+MAPPER+1344KB] — just apply the patch and then rename the patched file from .sms to .rom and you should be able to launch in it openMSX (should work on hardware too, but I have not yet tested that)
128 Total (KR) [HACK+MAPPER+2MB].zip (506 B)
128 Total (KR) [HACK+MAPPER+2MB].bps
128 Total (KR) [HACK+MAPPER+1344KB].zip (505 B)
128 Total (KR) [HACK+MAPPER+1344KB].bps

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Wed Apr 22, 2020 7:27 am
This is only somewhat SMS-related, but I also made a small patch to convert this same dump's MSX branch to the MSX "ASCII8" mapper, which allows it to be run more easily in MSX emulators and flash cartridges. This replaces the unusual MSX mapper this dump normally would require, where writing byte Y to address 0x6000 would map 0x4000-0x5FFF to Y, 0x6000-0x7FFF to Y XOR 1, 0x8000-0x9FFF to Y XOR 2, and 0xA000-0xBFFF to Y XOR 3.

I searched for the presumed MSX twin of this dump but could not find it. It's possible the (apparently undumped and nonfunctional) cartridge here is the same one, but really I have no idea: https://www.generation-msx.nl/software/zemina/128-in-1/release/5572/

edit: OOF! Beware, some of these games (just noticed it in Clapton) are not coded safely (wouldn't be surprised to learn the original cartridge had the same problem)
warning: The running MSX software has set unsafe PSG port directions.
Real (older) MSX machines can get damaged by this.

Aside: openmsx is pretty awesome for testing this and notifying about it

edit 2: well, apparently there is recent discussion about whether this will actually damage any MSX, but since I am not sure I will preserve the previous message. The recent discussion: https://www.msx.org/forum/msx-talk/software/games-that-may-damage-psg-chips?page...

edit 3: after learning more about openMSX MegaROM mapper autodetection I updated the patch to autodetect correctly in openMSX. Likely other emulators and flash cartridges (both untested so far) will need manual configuration to choose the correct mapper, though.

I also added a 2MB version using the ASCII16/ASC16 MegaROM mapper (again, fully functional in openMSX), which is attached here as a BPS patch inside a ZIP file in order to comply with forum attachment rules.

For both of these patches you'll likely need to rename the patched file from .sms to .rom to use it with emulators.

edit 4: Both MSX mapper adaptation flavors are now confirmed on real MSX2 hardware too. Note that the device and/or ROM loader will need to have support for very large ROMs (1M-2M, depending on which version you use.) In my testing SofaROM + MegaFlashROM SCC+ SD and OPFXSD + MegaFlashROM SCC+ SD were both able to handle all three sizes (1M ASC8, 1.3M ASC16 and 2M ASC16), and SofaROM + Mapper MegaRAM 512KB was able to handle the 1M version, apparently using on-demand paging from the SD card. Several other devices and combinations unfortunately did not work, but in my (admittedly limited) experience MSX flash device ROM dump compatibility is a bit finicky in any case.

edit 5: fmsx and MSX.emu on Android both work with the MSX mapper patches. MasterGear works with the SMS mapper patches
128 Total (KR) [HACK+ASCII16].zip (461 B)
128 Total (KR) [HACK+ASCII16].bps

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Mon Apr 27, 2020 6:39 am
I still don't understand the text engine used in the menu, but as a first test here's a small patch to translate the menu title to English. Apologies for the low quality art, I used the X11 "bitmap" tool to draw it using the touchpad.

There is in fact an ASCII (well, Korean variant of ISO 646 I guess?) font hiding in the original menu code too, but so far I haven't found it actually being used anywhere.

  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14745
  • Location: London
Reply with quote
Post Posted: Mon Apr 27, 2020 8:03 am
Korean text is built from a set of pieces and the font here makes me think they’ve used this as part of the text rendering engine to compose 16x16 characters from shared 8x8 tiles. This means it’s going to be hard to put English in there. If there is a Roman font supported then that would help.

It’s interesting to see how they implemented the “cheat variants” that typically fill these high-n pirate carts. It’s similar to how the Action Replay works. Others I’ve seen appear to change ROM values rather than RAM, which seems a bit harder to do. Are the games hacked to perform this RAM patching or is the cart somehow overlaying the behaviour on ROM?
  View user's profile Send private message Visit poster's website
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Mon Apr 27, 2020 8:46 am
Maxim wrote
Korean text is built from a set of pieces and the font here makes me think they’ve used this as part of the text rendering engine to compose 16x16 characters from shared 8x8 tiles. This means it’s going to be hard to put English in there. If there is a Roman font supported then that would help.


Yes, I was assuming that too but I'm also seeing fragments in the font data that suggest instead it might be uniquely encoding ("drawing") each character somehow — at least, I found some of the positional forms of jamos encoded more times than a simple johab-style font encoding would use. I experimentally tried replacing some of those character fragments, and so far each replacement appears exactly once in the menu, but I have only found a few of them in the ROM so far since I'm just staring at bit patterns :P

An 8x8 Roman font covering the printable ASCII/ISO 646 range is definitely present starting with space (0x20, " ") at linear address 0x00001d00 and using 8 bytes per character, but I don't know yet whether there is a text renderer that knows how to use it. It's really badly drawn, unfortunately, or perhaps was just run through a script of some kind to make it bolder/more TV-compatible, which unfortunately rendered a lot of the characters nearly unreadable. Won sign ₩ is encoded at the position that would be \ (0x5C, backslash/reverse solidus) in ASCII, as in UHC.

Quote
It’s interesting to see how they implemented the “cheat variants” that typically fill these high-n pirate carts. It’s similar to how the Action Replay works. Others I’ve seen appear to change ROM values rather than RAM, which seems a bit harder to do. Are the games hacked to perform this RAM patching or is the cart somehow overlaying the behaviour on ROM?


I'm not sure yet. Feel free to take a look if you like, it's attached upthread.

I'm still trying to trace through this code and understand it, but my current belief (guess, really) is that there is a VBLANK handler implemented in the MSX BIOS replacement area, which is a fixed address range not affected by the mapper system, and that in turn calls into RAM to run the current configured patch sequence. As part of switching from menu to RAM, the menu entry's patch sequence is written into this RAM. Then every time the VBLANK handler runs, all the patches get re-applied.

edit: and definitely no special mapper/cartridge hardware is needed for the patching, it works correctly in the version I converted to Sega mapper too
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Fri Feb 26, 2021 9:03 am
There is no Hangul printer in this ROM, instead the menu data is effectively a bitmap, stored as pattern data with an RLE feature starting at ROM address 0x2000. Format seems to be a either a repeat count (with MSB cleared) followed by a data byte, or a length count (with MSB set) followed by that many data bytes. I'm still working out the rest of the logical layout for this data, but it all seems to be stored in screen order except possibly with some skipping over the center gutter

edit: each left-right pair of entry titles is stored consecutively, with no delimiters. Each title is stored as the (RLE-compressed) 12 upper row 8x8-bit patterns followed by the 12 lower row 8x8-bit patterns, with the left title first, then the right title. I don't yet know how the consecutive title pairs are laid out

edit 2: each RLE'd pair of titles apparently has a NUL (0x00) byte appended if the RLE encoding ended up being an odd number of bytes
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sat Feb 27, 2021 4:48 am
Last edited by bsittler on Sat Mar 13, 2021 6:24 am; edited 1 time in total
Finally extracted the full menu using this horrible bash+python2+sed shell script:

bash$ (                       
cat <<.
/* XPM */
static char *noname[] = {
/* width height ncolors chars_per_pixel */
"96 2048 2 1",
/* colors */
". c #FFFFFF",
"X c #000000",
/* pixels */
.
    python2 -c 'import sys;start, length, pages, fn=sys.argv[1:];'\
'start, length, pages=int(start,0), int(length,0), int(pages,0);b=file(fn,"rb").read()[start:][:length];'\
'assert len(b)==length'\
$'\ndef unrle(i):\n o=""\n while len(i)>1:\n  c=ord(i[0])\n  i=i[1:]'\
$'\n  if c== 0: continue\n  if c & 0x80:\n   o+=i[:c%0x80]\n   i=i[c%0x80:]\n  else:\n   o+=c*i[0]\n   i=i[1:]\n return o\no=unrle(b);'\
$'p=[bin(ord(ch)|0x100)[len(bin(0)):].replace("0",".").replace("1","X") for ch in "".join(o[i] for i in range(len(o)))];'\
$'p="".join("".join(p[(i%12+12*(i//(12*8)))*8+(i//12)%8:][:1]) for i in range(len(p)));'\
$'print("\\n".join([p[i:][:8*12] for i in range(0, len(p), 8*12)][:pages*8*2*8*2]))' 0x2000 0x4000 8 128\ Total\ \(KR\).sms | \
sed 's/.*/"&",/; $ s/,$//'; \
echo '};' ) | xpmtoppm | pnmtopng > 128\ Total\ Menu.png

128 Total Menu.png (4.46 KB)
Menu rearranged into visual order for individual menu entries (warning: very tall! you may need to zoom and/or scroll)
128 Total Menu.png

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sat Feb 27, 2021 7:37 am
The purpose of the NUL bytes in the RLE isn't actually padding, instead they terminate an RLE segment. For some reason I don't yet understand, every menu screen is built out of two RLE segments — the first encodes the top menu item in each column, and the second encodes the rest of the menu items. The two RLE segments for each menu screen must be stored consecutively. A table with the addresses of the first RLE segment of each menu screen is found starting at linear ROM address 0x09CF, with the addresses naturally in terms of runtime memory layout, so 0x6000 for the first segment of the first menu screen (corresponding to linear ROM address 0x2000), 0x6623 for the second (... linear address 0x2623), etc. up to 0x8266 for the eighth (… linear address 0x4266). Of course the addresses are stored little-endian.

edit: correction, it's apparently three RLE segments per screen. One for the first row, one for the next four rows, and one for the last three.
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sat Feb 27, 2021 9:17 am
This really needs to be redrawn by something better than my awful shell scripts, but I've created a patch that at least allows the multicart's menu to at least be (mostly) read in English. Apply this patch after applying one of the mapper hacks. A few of the titles are squeezed too tightly by the shell script and became semi-unreadable, but I think it's nothing that couldn't be redone easily in Photoshop or Gimp now that the file format is documented. I am also aware that spacing and "kerning" are both really messed up right now.

Here's the shell script mess I used to create this first rough draft, which relies on a BDF font (in this case 6x13.bdf) and a bunch of netpbm tools:

bash$ ( (for t in Spelunker Lode\ Runner Penguin\ Land Kung-Fu\ II Tennis Road\ Fighter Kung-Fu Sky\ Jaguar Step\ UP Rally-X Picture\ Puzzle Tank\ Battalion Peetan Pyramid\ Warp Mr.\ Chin Hang-On Demon\ Castle\ Legend Mopiranger Pippols Fantasy\ Table\ Tennis Pooyan Mappy Pac-Man Fruit\ Search Heavy\ Boxing Galaxian Looping Zanac Zaxxon Flicky Goonies King\'s\ Valley Magical\ Tree Dig\ Dug Galaga Bomber\ Man Eagle Dragon\ Attack Chick Star\ Force TwinBee Wonder\ Boy Castle Bread\ Factory Circus Cabbage Hyper\ Rally Bosconian Q\*Bert Clapton King\&Balloon Bread\ Factory\ \#2 Sports\ II Bosconian\ #2 Trouble \"\ \#3 Warp\&Warp \"\ \#4 Antarctic\ Expedition \"\ \#5 Buta\ Pants Circus\ \#2 Cannon\ Ball \"\ \#3 Goonies\ \#2 Rally-X\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 Kung-Fu\ \#2 Mappy\ \#2 \"\ \#3 \"\ \#3 Cabbage\ \#2 \"\ \#4 \"\ \#3 \"\ \#5 Kung-Fu\ II\ \#2 Zanac\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 Road\ Fighter\ \#2 Mopiranger\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 Wonder\ Boy\ \#2 Demon\ Castle\ Legend\ \#2  \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 King\'s\ Valley\ \#2 Dig\ Dug\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 Flicky\ \#2 Lode\ Runner\ \#2  \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 \"\ \#6 \"\ \#6 \"\ \#7 \"\ \#7 \"\ \#8 \"\ \#8 \"\ \#9 \"\ \#9; do z=$((1+${z:-0})); echo "$(printf 0x%02X $(($z-1))) $z $t" >&2; pbmtext -nomargins -font 6x13.bdf -space $(( (12*8 - ${#t}*6)/(${#t}-1) )) "$t" | pnminvert | pnmpad -width 96 -height 16 | pnminvert | pamscale -width 96 -height 16 | pamditherbw | pamtopnm | pbmtoxbm | grep , | tr -d ' {};' | tr ',' '\n' | grep .;done) | python -c 'import sys;b="".join([chr(int(bin(0x100|int(ch,16))[2:][::-1][:-1],2)) for ch in sys.stdin.read().split()]);sys.stderr.write(repr(len(b)));assert len(b)>=12*16*16;assert not len(b)%(12*16*16),"Unexpected uncompressed input size";b="".join([b[(i%8)*12+(i//8)%12+(i//(8*12))*8*12] for i in range(len(b))]);'$'\ndef rle(i):\n o=""\n while len(i):\n  if len(i) == 1:\n   o+=chr(0x81)+i[0]\n   i=i[1:]\n   break\n  if i[0] != i[1]:\n   c=1\n   while len(i)>c+2 and i[c+2] != i[c+1] and c<0x7f:\n    c+=1\n   o+=chr(0x80+c)+i[:c]\n   i=i[c:]\n   continue\n  c=1\n  while len(i)>c and i[0]==i[c] and c<0x7f:\n   c+=1\n  o+=chr(c)+i[0]\n  i=i[c:]\n return o\ndef unrle(i):\n o=""\n while len(i)>1:\n  c=ord(i[0])\n  i=i[1:]\n  if c== 0: continue\n  if c==0x80: assert not c, "0x80 in RLE"\n  if c & 0x80:\n   o+=i[:c%0x80]\n   i=i[c%0x80:]\n  else:\n   o+=c*i[0]\n   i=i[1:]\n return o\no="".join(q + (chr(0) if ((i//(12*16*2)%8) in (0, 4, 7)) else "") for (q,i) in ((rle(b[i:i+12*16*2]),i) for i in range(0,len(b),12*16*2)))\nassert b==unrle(o), "Bad Round Trip"\nfile("/tmp/c","wb").write(b)\nsys.stdout.write(o)') > menu.ovl


This generates menu data in a file menu.ovl which needs to be overlaid onto the ROM starting at linear address 0x2000. I'm ashamed to admit I did that part manually using a hex editor. Call the result patched.sms.

Once the menu overlay has been manually applied, the next step is to generate a menu index and proof sheet from the patched ROM:


bash$
python2 -c 'import sys;start, length, pages, bias, fn=sys.argv[1:];start, length, pages, bias=int(start,0), int(length,0), int(pages,0), int(bias,0);b=file(fn,"rb").read()[start:][:length];assert len(b)==length'$'\ndef unrle(i):\n xil=len(i);yo={};xo={};o=""\n while len(i)>1:\n  c=ord(i[0])\n  i=i[1:]\n  if c== 0:\n   yo[len(o)]=xil-len(i)-1\n   continue\n  xo[len(o)]=xil-len(i)-1\n  if c & 0x80:\n   o+=i[:c%0x80]\n   i=i[c%0x80:]\n  else:\n   o+=c*i[0]\n   i=i[1:]\n return o,xo,yo\no,xo,yo=unrle(b);p=[bin(ord(ch)|0x100)[len(bin(0)):].replace("0",".").replace("1","X") for ch in "".join(o[i] for i in range(len(o)))];p="".join("".join(p[(i%12+12*(i//(12*8)))*8+(i//12)%8:][:1]) for i in range(len(p)));print("\\n".join([p[i:][:8*12]+(" %04X"%(i//(8*2*8*12)))+(":%04X"%(xo[i//8]+start+bias) if i//8 in xo else "")+("".join("|%02X=%04X"%(zj%8*12*2,start+bias+yo[zj])for zj in range(i//8,(i+8*12)//8)if zj in yo)) for i in range(0, len(p), 8*12)][:pages*8*2*8*2]))' 0x2000 0x4000 8 0x4000 patched.sms > proofsheet.txt


Then the list of 8 little-endian menu page graphics start addresses needs to be extracted. To do this I run another script:


bash$ echo $(sed -ne '1 p;/0:.*=/ p' proofsheet.txt  | sed -ne 's/.*:\(..\)\(..\).*/ \2\1/p')


In my case the output was
0060 A767 076F 7676 847D 6982 2387 FF8B
, but in any case these values must then be overlaid on the ROM starting at address 0x09CF. I used a hex editor for this part too.

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sat Feb 27, 2021 9:58 am
Changing to
pbmtext -font lucida_sans12.bdf -nomargins "$t" | pnminvert | pnmpad -width 96 -height 16 -left 0 | pnminvert | pamscale -width 96 -height 16 | pamditherbw -threshold -value 0.8
for the text ribbon generation part of the menu pipeline dramatically improved the legibility of the result. Updated patch attached. Still not perfect, but perhaps "good enough"?

  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14745
  • Location: London
Reply with quote
Post Posted: Sat Feb 27, 2021 11:03 am
I think it would be nicer to avoid the " parts, but that might exceed the space as it will compress less well.
  View user's profile Send private message Visit poster's website
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sat Feb 27, 2021 8:04 pm
Maxim wrote
I think it would be nicer to avoid the " parts, but that might exceed the space as it will compress less well.


Interesting! I used those "ditto marks" in an attempt to match the Korean text, but with the current text layout problems it is indeed ugly. I may try your suggestion and see whether it can be made to fit. Different font choices can dramatically influence the effectiveness of the RLE encoding of course! I could also see about optimizing my RLE encoder a bit since the current version is quite naïve…

Also FWIW the translation seems to work fine in an emulated MSX (openmsx) too — just apply the translation patch first to the original ROM, then apply the ASCII8 or ASCII16 patch (opposite order from SMS mapper hacks) since openmsx doesn't emulate the bizarre pirate mapper the MSX code path in this ROM was originally built for
  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sun Feb 28, 2021 1:47 am
I switched fonts yet again, this time to WenQuanYi Bitmap Song, and cleanup up the text layout a bit. Also Unicode works in menu entry titles now, though I don't use very much of it. I also used shorter but still well-known synonyms for some of the longer game names to minimize text-squeezing, so e.g. "Table Tennis" becomes "Ping-Pong" and "Demon Castle Legend" becomes "Knightmare" (my earlier attempt to squeeze in 魔城伝説 too failed to be readable due to not enough pixel rows or columns for these relatively complex kanji)

There's also a "ditto" parameter in the script now, and if you set that to an empty string no ditto marks will be used and instead the names will be repeated. I tried both and while I slightly prefer the "ditto" version both seem quite usable to me

Automation:

bash$ ( ditto=$'⋮\t'; font="wenquanyi_10pt.bdf"; margin=3; apply_ips 128\ Total\ \(KR\)\ \[HACK+MAPPER+PARTIAL\].ips 128\ Total\ \(KR\)\ \[HACK+TITLE\].sms 128\ Total\ \(KR\)\ \[HACK+MAPPER+PARTIAL+EN\].sms; ( ( t0=''; t1=''; for t in Spelunker Lode\ Runner Penguin\ Land Kung-Fu\ Ⅱ Tennis Road\ Fighter Kung-Fu Sky\ Jaguar Step\ UP Rally-X Picture\ Puzzle Tank\ Battalion Peetan Pyramid\ Warp Mr.\ Chin Hang-On Knightmare Mopiranger Pippols Fantasy\ Ping-Pong Pooyan Mappy Pac-Man Fruit\ Search Heavy\ Boxing Galaxian Looping Zanac Zaxxon Flicky Goonies King\'s\ Valley Magical\ Tree Dig\ Dug Galaga Bomber\ Man Eagle Dragon\ Attack Chick Star\ Force TwinBee Wonder\ Boy Castle Bread\ Factory Circus Cabbage Hyper\ Rally Bosconian Q\*Bert Clapton King\&Balloon Bread\ Factory\ \#2 Sports\ Ⅱ Bosconian\ #2 Trouble \"\ \#3 Warp\&Warp \"\ \#4 Antarctic\ Expedition \"\ \#5 Buta\ Pants Circus\ \#2 Cannon\ Ball \"\ \#3 Goonies\ \#2 Rally-X\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 Kung-Fu\ \#2 Mappy\ \#2 \"\ \#3 \"\ \#3 Cabbage\ \#2 \"\ \#4 \"\ \#3 \"\ \#5 Kung-Fu\ Ⅱ\ \#2 Zanac\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 Road\ Fighter\ \#2 Mopiranger\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 Wonder\ Boy\ \#2 Knightmare\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 King\'s\ Valley\ \#2 Dig\ Dug\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 Flicky\ \#2 Lode\ Runner\ \#2 \"\ \#3 \"\ \#3 \"\ \#4 \"\ \#4 \"\ \#5 \"\ \#5 \"\ \#6 \"\ \#6 \"\ \#7 \"\ \#7 \"\ \#8 \"\ \#8 \"\ \#9 \"\ \#9;
do
    tr=''; if [[ :"${t/\ \#/}" != :"$t" ]]; then
        tr="${t##*\ \#}"; t="${t%\ \#*}";
    fi; if [[ :"$t" = :'"' ]]; then
        t2="$t1";
    else
        t2="$t";
    fi; t1="$t0"; t0="$t2"; if [[ ${#ditto} = 0 ]]; then
        t="$t0";
    else
        if [[ :"$t" = :'"' ]]; then
            t="$ditto";
        fi;
    fi; uncenter='-left 0'; indent=''; if [[ :"$t" = :"${ditto:-'"'}" ]]; then
        uncenter=''; indent=$'\t';
    fi; z=$((1+${z:-0})); echo "$(printf 0x%02X $(($z-1))) $z $(printf "%s%s\t#%s" "$indent" "$t" "${tr:-1}")" 1>&2; lw=96; if [[ ${#tr} != 0 ]]; then
        lw=$((80+$margin));
    fi; echo "${tr:- }" | pbmtext -wchar -font "$font" -nomargins | pnmpad -height 16 -width $((16-2*margin)) -top 0 -right 0 -white | pamscale -width $((16-2*margin)) -height 16 2> /dev/null | pamditherbw -threshold -value 0.85 | pnmpad -height 16 -width 16 -top 0 -white > counter.pnm; echo "$t" | pbmtext -wchar -font "$font" -nomargins | pnmpad -width $(($lw-2*margin)) -height 16 $uncenter -top 0 -white | pamscale -width $(($lw-2*margin)) -height 16 2> /dev/null | pamditherbw -threshold -value 0.85 | pnmpad -width 96 -height 16 -left $margin -top 0 -white | pamcomp -xoff=80 -yoff=0 -alpha=counter.pnm -invert counter.pnm | pamtopnm | pbmtoxbm | grep , | tr -d ' {};' | tr ',' '\n' | grep .;
done ) | python -c 'import sys;b="".join([chr(int(bin(0x100|int(ch,16))[2:][::-1][:-1],2)) for ch in sys.stdin.read().split()]);sys.stderr.write(repr(len(b)));assert len(b)>=12*16*16;assert not len(b)%(12*16*16),"Unexpected uncompressed input size";b="".join([b[(i%8)*12+(i//8)%12+(i//(8*12))*8*12] for i in range(len(b))]);''
def rle(i):
 o=""
 while len(i):
  if len(i) == 1:
   o+=chr(0x81)+i[0]
   i=i[1:]
   break
  if i[0] != i[1]:
   c=1
   while len(i)>c+2 and i[c+2] != i[c+1] and c<0x7f:
    c+=1
   o+=chr(0x80+c)+i[:c]
   i=i[c:]
   continue
  c=1
  while len(i)>c and i[0]==i[c] and c<0x7f:
   c+=1
  o+=chr(c)+i[0]
  i=i[c:]
 return o
def unrle(i):
 o=""
 while len(i)>1:
  c=ord(i[0])
  i=i[1:]
  if c== 0: continue
  if c==0x80: assert not c, "0x80 in RLE"
  if c & 0x80:
   o+=i[:c%0x80]
   i=i[c%0x80:]
  else:
   o+=c*i[0]
   i=i[1:]
 return o
o="".join(q + (chr(0) if ((i//(12*16*2)%8) in (0, 4, 7)) else "") for (q,i) in ((rle(b[i:i+12*16*2]),i) for i in range(0,len(b),12*16*2)))
assert b==unrle(o), "Bad Round Trip"
sys.stdout.write(o)' ) > menu.ovl; python2 -c 'import sys;rom,ovl=sys.argv[1:];b=file(rom,"rb").read();m=file(ovl,"rb").read();b=b[:0x2000]+m+b[0x2000+len(m):];file(rom,"wb").write(b)' 128\ Total\ \(KR\)\ \[HACK+MAPPER+PARTIAL+EN\].sms menu.ovl; python2 -c 'import sys;start, length, pages, bias, fn=sys.argv[1:];start, length, pages, bias=int(start,0), int(length,0), int(pages,0), int(bias,0);b=file(fn,"rb").read()[start:][:length];assert len(b)==length''
def unrle(i):
 xil=len(i);yo={};xo={};o=""
 while len(i)>1:
  c=ord(i[0])
  i=i[1:]
  if c== 0:
   yo[len(o)]=xil-len(i)-1
   continue
  xo[len(o)]=xil-len(i)-1
  if c & 0x80:
   o+=i[:c%0x80]
   i=i[c%0x80:]
  else:
   o+=c*i[0]
   i=i[1:]
 return o,xo,yo
o,xo,yo=unrle(b);p=[bin(ord(ch)|0x100)[len(bin(0)):].replace("0",".").replace("1","X") for ch in "".join(o[i] for i in range(len(o)))];p="".join("".join(p[(i%12+12*(i//(12*8)))*8+(i//12)%8:][:1]) for i in range(len(p)));print("\n".join([p[i:][:8*12]+(" %04X"%(i//(8*2*8*12)))+(":%04X"%(xo[i//8]+start+bias) if i//8 in xo else "")+("".join("|%02X=%04X"%(zj%8*12*2,start+bias+yo[zj])for zj in range(i//8,(i+8*12)//8)if zj in yo)) for i in range(0, len(p), 8*12)][:pages*8*2*8*2]))' 0x2000 0x4000 8 0x4000 128.sms > proofsheet.txt; python2 -c 'import sys;fn,at,h=sys.argv[1:];b=file(fn,"rb").read();at=int(at,16);h=h.decode("hex");b=b[:at]+h+b[at+len(h):];file(fn,"wb").write(b)' 128\ Total\ \(KR\)\ \[HACK+MAPPER+PARTIAL+EN\].sms 09cf $(echo $(sed -ne '1 p;/0:.*=/ p' proofsheet.txt  | sed -ne 's/.*:\(..\)\(..\).*/ \2\1/p') | tr -d ' '); echo )

128 Total (KR) [HACK+EN+NODITTO] #7.png (18.02 KB)
One representative screen from the "NODITTO" version
128 Total (KR) [HACK+EN+NODITTO] #7.png

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sun Feb 28, 2021 2:17 am
One more example, this time with a fixed-width font (6x13.bdf) that fits most of the titles without compressing them, though at the expense of margins

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sun Feb 28, 2021 4:48 am
Another I maybe like a bit better, using WQY Bitmap Song with the Unicode ditto mark 〃 common in CJK typography. That character is also used in the original menu, though that was set using a different font I have yet to identify

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sat Mar 13, 2021 12:19 am
I also tried ImageMagick which lets me use TrueType fonts. On a Mac with Arial and ArialNarrow fonts I got pretty readable results.

Separately I was able to draw a version of the menu using the 8x8 fixed-width bitmap Latin font built into the cartridge. It's a bit ugly but actually provides the most readable results yet on the original Game Gear screen in GG-SMS mode, where narrow horizontal lines would show up in the wrong color due to only the RG-, R-B, or -GB color components being visible depending on which SMS column the pixel is from. For Game Gear I built SG2GG patches that set up the color palette in either GG-SMS mode or native GG mode. For native GG mode a few of the games are surprisingly playable but most rely on too much of the screen content being visible

I got the MSX code path working with the Konami SCC mapper, although the ROM itself is larger than Konami's SCC chips support so it's only actually functional with an emulator or flash cart that supports this "super-MegaROM" 1MB "SCC" mapper. That and the other MSX mapper patches (ASCII8 and ASCII16) are also modified to include "dead" code to force correct mapper autodetection

Since there are quite a few patches at this point they are in a ZIP file. Patches that result in a larger output than the input are given in BPS format too, otherwise you will need to enlarge the ROM yourself as described earlier in the thread. 128 Total (KR) [HACK+2MB].bps can enlarge the ROM for you.

All of the BPS patches (which includes some that are combinations of multiple IPS patches) are meant to be applied to:

1.0M '128 Total (KR).sms'
Checking for export header with matching CRC... NO
sha256:d398fff1599211711d037a073db02b131c7d00bbdd13e64abbd816545c3b4dee 128 Total (KR).sms
sha1:07d4be0635bab0a6523019c8ce2c23d9cd3b3e1d 128 Total (KR).sms
md5:6dddeb0bb21622b6ee1deb4c4870f801 128 Total (KR).sms
mekacrc:0707EB856DC38BC7 128 Total (KR).sms
crc32:ba5ec0e3 128 Total (KR).sms

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sat Mar 13, 2021 6:14 am
Here are some simulations of what the first screen of each menu looks like on a Game Gear with the stock LCD which show just how advantageous a bold font is.

NOTE: these still don't simulate the RGB subpixel arrangement, so they'll only look really "right" when viewed at 1 pixel = 1 pixel scale on a modern display with the same RGB subpixel arrangement. However this same inaccuracy plagues every Game Gear emulator screenshot I've seen (regardless of whether taken in GG-SMS mode) so I don't view it as particularly serious. The display aspect ratio will be inauthentic for these, of course.

The "aspect-corrected scaled" versions are just enlarged 6x horizontally and 5x vertically to simulate a 4:3 display aspect ratio (the Game Gear seems to be that, more or less) but of course this means they won't work in subpixel terms

The horizontal subpixel dropping effects were confirmed to match this on real hardware. I don't know how the vertical averaging that displays 3 SMS pixel rows in 2 GG pixel rows works in hardware so this simulation just uses pamscale's blending to hackily approximate that. Maybe it's documented in another thread somewhere on this site?

The screenshots used as input were captured in Emulicious running the .sms ROM with Game Gear hardware selected. Top and bottom border areas were guessed since Emulicious doesn't show or store those for its Game Gear SMS emulation & screenshots.

Actual hardware shows the VDP border area for 12 SMS pixels / 8 GG pixels both above and below the 192 SMS pixel/128 GG pixel viewport, but I haven't found an emulator that emulates this.

anytopnm \
 < Emulicious/screenshots/128\ Total\ \(KR\)\ \[HACK+MAPPER+2MB+EN+8PX\]\ SG2GG.png | \
 pnmmargin 12 | \
 pamcut -left $((12+8)) -right $((12+247)) | \
 pamenlarge -xscale 2 | \
 ppmtorgb3 ; \
rgb3toppm \
 <( pnmpad -left 3 -right 0 noname.red ) \
 <(pnmpad -left 2 -right 1 noname.grn ) \
 <(pnmpad -left 1 -right 2 noname.blu ) | \
 pamscale -xscale 0.333333333333333333333333333 -nomix | \
 pamcut -left 1 | \
 pamscale -yscale 0.666666666666666666666666666 | \
 pamtopng \
 > 128\ Total\ \(KR\)\ \[HACK+MAPPER+2MB+EN+8PX\]\ SG2GG-lcdemu.png

128 Total (KR) [HACK+MAPPER+2MB+EN] SG2GG-lcdemu.png (3.45 KB)
English menu with Arial & ArialNarrow fonts (GG LCD simulation)
128 Total (KR) [HACK+MAPPER+2MB+EN] SG2GG-lcdemu.png
128 Total (KR) [HACK+MAPPER+2MB+EN+8PX] SG2GG-lcdemu.png (3.35 KB)
English menu with 8x8-pixel bitmap font (GG LCD simulation)
128 Total (KR) [HACK+MAPPER+2MB+EN+8PX] SG2GG-lcdemu.png
128 Total (KR) [HACK+MAPPER+2MB] SG2GG-lcdemu.png (3.26 KB)
Original Korean menu (GG LCD simulation)
128 Total (KR) [HACK+MAPPER+2MB] SG2GG-lcdemu.png
128 Total (KR) [HACK+MAPPER+PARTIAL+EN] SG2GG-hw.png (495.81 KB)
On older GG hardware (Sega branded)
128 Total (KR) [HACK+MAPPER+PARTIAL+EN] SG2GG-hw.png
128 Total (KR) [HACK+MAPPER+PARTIAL+EN] SG2GG-majescohw.png (323.78 KB)
On slightly newer GG hardware (Majesco branded)
128 Total (KR) [HACK+MAPPER+PARTIAL+EN] SG2GG-majescohw.png
128 Total (KR) [HACK+MAPPER+2MB+EN] SG2GG-lcdemu.png (8.6 KB)
Aspect-corrected scaled English menu with Arial & ArialNarrow fonts (GG LCD simulation)
128 Total (KR) [HACK+MAPPER+2MB+EN] SG2GG-lcdemu.png
128 Total (KR) [HACK+MAPPER+2MB+EN+8PX] SG2GG-lcdemu.png (8.45 KB)
Aspect-corrected scaled English menu with 8x8-pixel bitmap font (GG LCD simulation)
128 Total (KR) [HACK+MAPPER+2MB+EN+8PX] SG2GG-lcdemu.png
128 Total (KR) [HACK+MAPPER+2MB] SG2GG-lcdemu.png (8.47 KB)
Aspect-corrected scaled original Korean menu (GG LCD simulation)
128 Total (KR) [HACK+MAPPER+2MB] SG2GG-lcdemu.png

  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sat Mar 13, 2021 7:00 am
Here's a cheap simulation of the RGB subpixel effect. It takes a 160x144 image as input and produces a 960x720 image (4:3 screen aspect ratio) as output with a simulation of RGB subpixel geometry

anytopnm \
 < screenshot.png | \
 pamscale -nomix -yscale 5 | \
 pamstretch -xscale 6 | \
 ppmtorgb3; \
rgb3toppm \
 <( pnmpad -left 0 -right 6 noname.red ) \
 <(pnmpad -left 2 -right 4 noname.grn ) \
 <(pnmpad -left 4 -right 2 noname.blu ) | \
 pamcut -left 3 -right 962 | \
 pamtopng \
 > screenshot-rgbscaled.png


  View user's profile Send private message
  • Joined: 22 Apr 2018
  • Posts: 530
Reply with quote
Post Posted: Sun Jun 06, 2021 10:49 pm
One more patch, this one primarily useful to those trying to use this on (non-Zemmix) MSX machines: it allows the menu to be controlled by cursor keys and space in addition to the (already supported) joyport A

This can be applied on top of the unpatched ROM or to any of the patched versions. Likely it will be most useful in conjunction with one of the MSX mapper patches

  View user's profile Send private message
  • Site Admin
  • Joined: 08 Jul 2001
  • Posts: 8652
  • Location: Paris, France
Reply with quote
Post Posted: Mon Aug 23, 2021 9:33 pm
Ok, some progress.
I have merged the code for your mapper now:
https://github.com/ocornut/meka/commit/5d8e204eb709d67b7ad26cc37cdd8ec33dd0e85a

This is now mapper 19 (not 18 as per the time you made you patch).

I've currently called this "128 Hap" (not "128 Total") as this is how we frequently transcribed it:
https://www.smspower.org/Games/PiguWang7Hap-SMS

But google translate tells me it is "Hab" so this may need to be researched.

As we move onward with discovering mappers we will probably:
- End up renaming them all
- Would be good to reorganize some of MEKA code. Kudos to you for finding you way through this 20+ years old codebase from hell :)
  View user's profile Send private message Visit poster's website
Reply to topic



Back to the top of this page

Back to SMS Power!