You are not logged in.

Read the FAQ and Knowledge Base before posting.
We won't make a 3DS/2DS emulator.

#1 Re: Technical » Details on various slot2 accessories » 2024-09-06 22:28:54

The Wave Scanner is an accessory that connects to the DS through the microphone connector used by one game(technically 3), Ryuusei no Rockman. It has minigames that can be played on it to level it up. That level can be transferred to the DS to power up your character. It can also scan "battle cards", which have barcodes on them, and send them to the DS. Its operation is simple and requires no emulation changes as long as the wav samples are encoded at the exact right bitrate(that's a discussion for another topic).
I've written a Python script that will generate wav samples for every card and a variety of Wave Scanner devices.
https://gist.github.com/windwakr/058a78 … a0b950771c
The wavs are directly usable in DeSmuME as-is. Enjoy.

Demonstration video. I power up with a level 50 Pegasus Wave Scanner then enter battle and scan an Air Spreader card.
https://webmshare.com/play/ZJB6w


-----

This one is just partially understood.

The Direction Sensing Card is the(official? unofficial?) name for a special oversized slot-1 game card used by the game Hoshizora Navi. The game communicates with the built-in AK8973 compass chip over game card SPI commands. I haven't seen the internals of one of these game cards so I'm not sure how it's wired up.

The games uses these SPI commands

0x10 - ??
0x11 - ??
0x2X - Write, the lower nibble indicates the amount of bytes
0x4X - ??
0x60 - Read byte
0x80 - ?? Game halts until it's 0 when reading
0x82 - ?? Game halts until it's 0 when reading
0x85 - ?? Game writes 0x98 here
0x88 - Seems to be used to set the reset pin on the compass chip?

Using just the 0x2X, 0x60, and 0x8X commands you can emulate enough of the chip to get the game to successfully read/write values.

The game also has an accelerometer chip. Again, as I haven't seen inside the game card I'm unsure of what the exact chip is. It writes to registers 0x20-0x22, checks the status of register 0x27, then reads registers 0x28-0x2D for the accelerometer outputs. Turns out there's dozens of chips with this exact configuration. I've been referring to the datasheet for LIS3DH, although it's highly unlikely that's the exact chip inside. The game reads from the accelerometer through spi 02 commands. It writes the (register number | 0xC0) then reads the result. On successive reads the next register is read. I haven't traced how register writes are performed. I'm also unsure how these commands are differentiated from BM_CMD_WRITELOW commands.

What's the point of emulating this functionality when the game already has a freelook mode using the dpad? It would be useful for Android emulation, I suppose.

For future reference this may be a good base for generating realistic-looking magnetometer output
https://github.com/NavPy/SensorSim/blob … sim_mag.py
It would still need to be shaped into whatever the AK8973 outputs(with its offset/gain stuff).
I'd imagine a stationary rotating accelerometer wouldn't be too difficult to simulate the output of.

Here's a video demonstration with very poorly coded inputs. The first half is spent "calibrating" the device, which just consists of rotating it around a bit until the compass icon turns green.
https://webmshare.com/play/oOvY6

#2 Re: Support » Accidentaly saved over a load state » 2024-09-01 15:30:24

Try using the "Load state from" option and selecting your backup state in there. You should also be aware that you shouldn't really be using savestates with the Pokemon games, as they tend to break over time. Make regular in-game saves and maybe even back up your battery folder every now and then.

#3 Technical » Mic wave samples on Windows (ft. Bangai-O Spirits) » 2024-08-31 18:49:25

windwakr
Replies: 0

Wave sample handling on Windows is completely busted. It doesn't take into account either the samplerate of the input wave file or the sampling rate the game is performing. Most wave samples will be fed to the game way too slowly/quickly and not have the intended effect. I've got some sample code to try and fix this which is in the archive at the end of the post. It's not quite polished enough to submit a PR, more of a proof of concept. Various things like the savestate code would need modification and I cba. I'm also not sure if nds_arm7_timer is the value I should be using for "overall arm7 clock cycles" but I couldn't see anything else keeping track of it when looking around.



Those changes ALMOST let Bangai-O Spirits start reading level data. Almost. The game samples the mic at roughly ~32.7kHz, checking for a 1024Hz sine wave. What I've found from debugging is that DeSmuME sometimes has a massive gap in cycle times from sample to sample. Tens of thousands. This leads to entire bits of level data being skipped, breaking the whole transfer. My somewhat hacky solution is to log the first 21(arbitrarily chosen) sample cycle differences when the user presses the microphone button. It then takes the median value to discard outliers and uses that as the mic sampling frequency until the user releases the microphone button.

Here's a demonstration of that. I added in a volume slider too, because the game is quite picky about that:
https://files.catbox.moe/i3p5jv.webm



Level audio files, DeSmuME binary, source changes:
https://files.catbox.moe/fa5ofy.zip

#4 Re: Technical » Details on various slot2 accessories » 2024-08-12 01:38:38

Probably the most uselss add-on.

The Bey Point Reader(BPR) is an accessory used by some Japanese-only Beyblade games to read the data off of a device called a Beypointer. It can also send your name to the Beypointer. What is a Beypointer? I have no clue.

Minimal code for detection is returning 0x96 on byte-sized reads and 0xF500 on word-sized reads from 0x080000BE.

The BPR uses these addresses for communication, all word-sized.

0x0815FE00, 0x0815FE02..0x0815FE0E : Buffer for communication packets
0x0815FE10 : Status/Config?
0x0815FE12 : Size of packet in buffer, written from DS
0x0815FE14 : Size of packet in buffer, written from BPR
0x0815FE16 : ???
0x0815FEFE : Read from before every read/write, discarded. Clocks the Beypointer?

pic: https://files.catbox.moe/uxx5lv.png
save file for Metal Fight Beyblade (J): https://files.catbox.moe/f1qhju.zip
To access the BPR feature, バトル -> たいせんメニュー -> BPコラしん

Here is minimal code for working read/write, though there's no persistent storage after closing the emulator.
This test code was written in place of the guitar grip code out of laziness.

/*
	Copyright (C) 2009 CrazyMax
	Copyright (C) 2009-2013 DeSmuME team

	This file is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This file is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with the this software.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "../slot2.h"
//#include "../armcpu.h"

#define ARR_SIZE 0x30 //IDK. Game never reads higher than around 0x2D
//0-2 = some points, 3-5 = some other points, 6-7 = wins, 8-9 = losses, 0xA-0xB = "game overs", 0xC = battle something(can't read it)
// 0x18-0x27 = 8 character name in Shift-JIS, only supports some characters
//little endian
static u8 arr[ARR_SIZE] = { 0xA8, 0x16, 0x03, 0xF0, 0x06, 0x00, 0x55, 0xA4, 0x5C, 0x11, 0x00, 0x08, 0x37, 0x0D, 0x0E, 0x0F,
						    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x82, 0x60, 0x82, 0x61, 0x82, 0x62, 0x82, 0x63,
						    0x82, 0x64, 0x82, 0x65, 0x00, 0x00, 0x00, 0x00, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,};

class Slot2_GuitarGrip : public ISlot2Interface
{
private: 
	int processCommand;
	u16 status; //idk
	u8 buf[8] = { 0 };
	u8 bufSize = 0;

	u8 reverseNibble(u8 val) { return (val & 0xF0) >> 4 | (val & 0x0F) << 4; }

	u16 processBpr(u32 addr)
	{
		u16 outWord = 0;
		u8 pos;
		u8 size;
		if (processCommand) {
			processCommand = 0;
			u8 cmd = buf[0];

			switch (cmd) {
			case 0x40: //?
				break;
			case 0x44: //Read
				pos = reverseNibble(buf[1]);
				size = reverseNibble(buf[2]);
				for (int i = 0; i < size; i++) { //Copy data into buf
					if ((pos+i < ARR_SIZE) && (i+1 < 8))
						buf[i + 1] = reverseNibble(arr[pos + i]);
				}
				buf[0] = 0x46;
				bufSize = size + 1;
				status = 0x02;
				printf("BPR READ, POS: %02X, SIZE: %02X\n", pos, size);
				break;
			case 0x45: //Write
				pos = reverseNibble(buf[1]);
				size = reverseNibble(buf[2]);
				for (int i = 0; i < size; i++) { //Copy data from buf
					if ((pos+i < ARR_SIZE) && (i+3 < 8))
						arr[pos+i] = reverseNibble(buf[i + 3]);
				}
				buf[0] = 0x46; //Only need to change the first byte of the buffer, the rest is fine
				bufSize = size + 3;
				status = 0x02;
				printf("BPR WRITE, POS: %02X, SIZE: %02X\n", pos, size);
				break;
			case 0x46: //Data
				buf[0] = 0x4C;
				bufSize = 1;
				status = 0x02;
				break;
			case 0x4C: //?
				status = 0x04;
				break;
			default:
				outWord = 0;
				break;
			}
		}

		if ((addr >= 0x0815FE00) && (addr <= 0x815FE0E)) {
			u32 tmp = (addr & 0xF) / 2;
			outWord = buf[tmp];
		} else if (addr == 0x0815FE10) {
			outWord = status;
		}else if (addr == 0x0815FE14) {
			outWord = bufSize;
		}
		return outWord;
	}
	
public:
	virtual Slot2Info const* info()
	{
		//static Slot2InfoSimple info("Guitar Grip", "Guitar Grip for Guitar Hero games", 0x04);
		static Slot2InfoSimple info("Bey Point Reader", "BPR", 0x04);
		return &info;
	}

	virtual void connect()
	{
		processCommand = 0;
		bufSize = 0;
		status = 0;
	}

	virtual void writeWord(u8 PROCNUM, u32 addr, u16 val)
	{
		//printf("writeWord: %08X = %04X\n", addr, val);

		if ((addr >= 0x0815FE00) && (addr <= 0x0815FE0E)) {
			u32 tmp = (addr & 0xF) / 2;
			buf[tmp] = val & 0xFF;
		}

		if ((addr == 0x0815FE12)) {
			bufSize = val & 0xFF;
			processCommand = 1;
		} else if (addr == 0x0815FE16) {
			status = 0x0; //?
		}
	}

	virtual u8	readByte(u8 PROCNUM, u32 addr) 
	{
		//if (addr != 0x080000B2)
		//	printf("readByte: %08X\n", addr);

		return 0x96; //Detection
	}
	
	virtual u16	readWord(u8 PROCNUM, u32 addr)
	{
		u16 outWord = 0x0000;

		if (addr == 0x080000BE) { //Detection
			outWord = 0xF500;
		}

		if ((addr >= 0x815FE00) && (addr < 0x815FEFE))
			outWord = processBpr(addr);

		//if ((addr > 0x080000BE) && (addr != 0x0815FEFE))
		//	printf("readWord: %08X = %04X\n", addr, outWord);// from %08X(ARM%c)\n", addr, outWord, ARMPROC.instruct_adr, PROCNUM ? '7' : '9');

		return outWord;
	}
	virtual u32	readLong(u8 PROCNUM, u32 addr) { if (addr != 0x080000AC) printf("readLong: %08X\n", addr);  return 0x0; }
};

ISlot2Interface* construct_Slot2_GuitarGrip() { return new Slot2_GuitarGrip(); }

void guitarGrip_setKey(bool green, bool red, bool yellow, bool blue)
{
	//const u8 g = (green)	? (1 << 6) : 0;
	//const u8 r = (red)		? (1 << 5) : 0;
	//const u8 y = (yellow)	? (1 << 4) : 0;
	//const u8 b = (blue)		? (1 << 3) : 0;
	//
	//guitarKeyStatus = ~(g | r | y | b);
}

void guitarGrip_setKey(u8 theKeys)
{
	//guitarKeyStatus = theKeys;
}

#5 Technical » Details on various slot2 accessories » 2024-08-05 13:59:59

windwakr
Replies: 6

I have no desire to properly implement these so I'll just leave this info here, where it is unlikely to be seen by anyone. tongue


The Motion Pack was used by one game, Tony Hawk's Motion. The bare minimum for detection to succeed is to return 0xFCXX on word sized reads. The game will enter a loop polling bytes from 0x0A000000. I don't fully understand this part but you can get it to continue by letting it read [0x00, 0x03, 0x03, 0x03, 0x03]. It will then read bytes from the same address for the X, Y, and Z acceleration values. These are served two bits at a time(starting from the top of the byte) in the least significant bits of the return value. for a total of 12 reads. A value of 0x80 seems to be the neutral point for each acceleration value? It will then go back to polling the address for those 5 values from earlier. This is enough to fully play Tony Hawk and its included "Hue Pixel Painter" minigame. In my example video I had hooked up Lua bindings and sent in my controller analog stick as the X/Y acceleration values through a Lua script.

One additional note is that depending on the values received, the game can change how it accesses the device. If you only send 0x03 on byte reads initially then the Tony Hawk portion of the game will actually switch over to reading it as if it were an NDSmotion Motion Pak. See libnds for how that works: https://github.com/devkitPro/libnds/blo … dsmotion.c
This mode is not necessary to emulate for playing the game as you can just send the sequence I mentioned earlier instead to have it read the values sequentially from 0x0A000000.

Video: https://files.catbox.moe/wkpnb6.webm


The Bayer Didget was used by one game, Knock 'Em Downs: World's Fair. The bare minimum for detection to succeed is to return 0xF3XX on word sized reads. The game then performs two reads from 0x0A000000 and expects them to be 0xBD and 0xDA. It then works like a glucoboy, which shonumi has reverse engineered and written a blog post about. After it writes to 0x0A000000 to pick a register to read, you trigger a gba slot interrupt( setIF(PROCNUM, 0x2000); ) to tell it that a byte is ready for it to read from 0x0A000000. I'm not sure if the registers it reads from are exactly the same as the glucoboy, so it would take a bit of exploration. Overall though, it's a simple add-on to emulate.

Pic: https://files.catbox.moe/qw5n9c.png

#6 Re: Technical » About slot-2. A little wisdom please. » 2023-10-02 20:01:07

Here is every barcode supported by Mushi King, which I extracted from the game.
https://windwakr.github.io/mushikingds.html


Here are barcodes for Oshare Majo.
https://windwakr.github.io/loveandberry.html


And here are the codes for the last HCV-1000 game, Card de Asobu.
https://windwakr.github.io/cardasobu.html

#7 Re: Technical » About slot-2. A little wisdom please. » 2023-10-02 19:04:00

EDIT:
DeSmuME now supports the Sega Card Reader (HCV-1000) in the latest nightly builds(Windows only).
Set the GBA slot to the Sega Card Reader then set up a hotkey and the barcode.

#8 Re: General » freelook Lua script » 2023-06-26 03:47:33

ARMarioBroz7 wrote:

hey windwakr can you create another lua script that has the ability to move the camera using keyboard?

https://files.catbox.moe/9gsn8r.webm
This adds keyboard support and mouse aiming. No thought was put into the controls other than avoiding DeSmuME's default hotkeys, so feel free to adjust them.

--Configure the keyboard controls below
forwardsKey = "U";  --These keys move the camera forward, backward, left, right, up, down
backwardsKey = "J"; --^^^
leftKey = "H";      --^^^
rightKey = "K";     --^^^
upKey = "T";        --^^^
downKey = "G";      --^^^
rotateUpKey = "numpad8";     --These keys rotate the camera
rotateDownKey = "numpad5";   --^^^
rotateLeftKey = "numpad4";  --^^^
rotateRightKey = "numpad6";  --^^^
pitchOffsetKey = "Y"; --Sets the pitch offset to correct for unwanted roll. Aim at the horizon and press to set it.
resetKey = "L"; --Resets everything
movScaleDownKey = "numpad0"; --Adjusts movement scaling down
movScaleUpKey = "numpad1";   --Adjusts movement scaling up
rotScaleDownKey = "numpad2"; --Adjusts rotation scaling down
rotScaleUpKey = "numpad3";   --Adjusts rotation scaling up
scaleMultDownKey = "numpad7"; --Makes the scale adjustment keys 10x less effective
scaleMultUpKey = "numpad9";   --Makes the scale adjustment keys 10x more effective
mouseAimButton = "rightclick"; --Button for mouse aiming. May be leftclick, rightclick, middleclick or a keyboard key



--Gamepad controls assume you have an Xbox style controller

--  Left stick moves left/right and forward/back
--  Right stick rotates
--  Triggers move up down
--  
--  A button resets position, rotation, and pitch offset
--  B button resets rotation and pitch offset
--  X sets pitch offset
--  Y resets the movement and rotation scale
--  
--  Dpad Up/Down adjusts the movement scale
--  Dpad Left/Right adjusts the rotation scale
--  Hold the left or right bumpers to make smaller or bigger changes to the scales
--  
--  Setting the pitch offset is important. Without doing so, your view will roll around as you look left and right in most games.
--  Angle your view towards the "horizon" and rotate the camera 90 degrees to the left or right. Then hold X and adjust up/down a little until everything's level.

joyID = -1; --Controller ID to use. The script will attempt to find a suitable controller, but you can manually set this if you wish
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0; --used to correct for the initial pitch of the camera
xpos = 0.0; --coordinates in a virtual 3D space
ypos = 0.0; --^^^
zpos = 0.0; --^^^
matAdjustment = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};
movscale = 1.0;
rotscale = 1.0;
cooldown = 0;

function MatrixMult4x4(m1, m2) --sorta copied/inspired from some lua matrix library
    local mtx = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    for i=1,4 do
        for j=1,4 do
            local num = 0
            for n=1,4 do
                num = num + m1[(i-1)*4+n] * m2[(n-1)*4+j]
            mtx[(i-1)*4+j] = num
            end
        end
    end
    return mtx;
end

function Rotate(angle,x,y,z)
    local s = math.sin(math.rad(angle));
    local c = math.cos(math.rad(angle));
    return {(1-c)*x*x+c, (1-c)*y*x-s*z, (1-c)*z*x+s*y, 0,
            (1-c)*x*y+s*z, (1-c)*y*y+c, (1-c)*z*y-s*x, 0,
            (1-c)*x*z-s*y, (1-c)*y*z+s*x, (1-c)*z*z+c, 0,
            0, 0, 0, 1};
end

function vecrotx(angle, vx, vy, vz) --used for pitch correction in the functions below
    return vx,
            vy * math.cos(math.rad(angle)) - vz * math.sin(math.rad(angle)),
            vy * math.sin(math.rad(angle)) + vz * math.cos(math.rad(angle));
end

function ApplyMovement(fX, fY, fZ, l)
    fX, fY, fZ = vecrotx(pitchoffs, fX, fY, fZ);
    
    local mag = math.sqrt(fX*fX + fY*fY + fZ*fZ);
    fX = fX / mag; fY = fY / mag; fZ = fZ / mag;
    fX = fX * l; fY = fY * l; fZ = fZ * l;
    xpos = xpos + fX; ypos = ypos + fY; zpos = zpos + fZ;
end

function MoveForward(l) --sorta based off https://www.gamedev.net/forums/topic/415144-get-forward-right-up-vectors-from-pitch-yaw-roll/
    local fX = math.sin(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs));
    local fY = -math.sin(math.rad(pitch+pitchoffs));
    local fZ = -math.cos(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs));
    
    ApplyMovement(fX, fY, fZ, l);
end

function MoveUp(l)
    local fX = math.sin(math.rad(yaw)) * math.cos(math.rad(pitchoffs+90));
    local fY = -math.sin(math.rad(pitchoffs+90));
    local fZ = -math.cos(math.rad(yaw)) * math.cos(math.rad(pitchoffs+90));
    
    ApplyMovement(fX, fY, fZ, l);
end

function MoveRight(l)
    local fX = math.sin(math.rad(yaw+90));
    local fY = 0;
    local fZ = -math.cos(math.rad(yaw+90));
    
    ApplyMovement(fX, fY, fZ, l);
end

function on3d()
    emu.set3dtransform(2,matAdjustment);
end
emu.register3devent(on3d);
emu.set3dtransform(1,nil);

if joyID == -1 then
    for i = 0, 15 do
        c = controller.get(i);
        if c.x and c.y and c.z and c.u and c.r then --simple check that only looks for all the needed axes
            joyID = i;
            print("Found suitable controller, id " .. i);
            break;
        end
    end
else
    print("Using manually set controller, id " .. joyID);
end
if joyID == -1 then
    print("No suitable controller found!");
end

while true do
    if joyID ~= -1 then
        local joy = controller.get(joyID)
        
        scalemult = 1;
        if joy["5"] then --Left bumper
            scalemult = 0.1;
        elseif joy["6"] then --Right bumper
            scalemult = 10;
        end
        if joy.up and cooldown==0 then --Dpad
            cooldown = 10;
            movscale = movscale + (0.1 * scalemult);
            print("Movement scale: " .. movscale);
        end
        if joy.down and cooldown==0 then --Dpad
            cooldown = 10;
            movscale = movscale - (0.1 * scalemult);
            print("Movement scale: " .. movscale);
        end
        if joy.right and cooldown==0 then --Dpad
            cooldown = 10;
            rotscale = rotscale + (0.1 * scalemult);
            print("Rotation scale: " .. rotscale);
        end
        if joy.left and cooldown==0 then --Dpad
            cooldown = 10;
            rotscale = rotscale - (0.1 * scalemult);
            print("Rotation scale: " .. rotscale);
        end
        
        if(math.abs(joy.x)>0.25) then --Left Stick
            MoveRight(-joy.x * movscale);
        end
        if(math.abs(joy.y)>0.25) then --Left Stick
            MoveForward(joy.y * movscale);
        end
        if(math.abs(joy.z)>0.25) then --Triggers
            MoveUp(-joy.z * movscale);
        end
        if(math.abs(joy.u)>0.25) then --Right Stick
            yaw = (yaw + (joy.u * rotscale)) % 360;
        end
        if(math.abs(joy.r)>0.25) then --Right Stick
            pitch = (pitch + (joy.r * rotscale)) % 360;
        end
        
        if joy["1"] and cooldown==0 then --A button
            cooldown = 20;
            yaw = 0.0;
            pitch = 0.0;
            pitchoffs = 0.0;
            xpos = 0.0;
            ypos = 0.0;
            zpos = 0.0;
            movscale = 1.0;
            rotscale = 1.0;
            print("Reset everything");
        end
        if joy["2"] and cooldown==0 then --B button
            cooldown = 20;
            yaw = 0.0;
            pitch = 0.0;
            pitchoffs = 0.0;
            print("Reset rotations");
        end
        if joy["3"] then --X button
            pitchoffs = -pitch;
        end
        if joy["4"] and cooldown==0 then --Y button
            cooldown = 20;
            movscale = 1.0;
            rotscale = 1.0;
            print("Movement/rotation scales reset");
        end
        if joy["8"] and cooldown==0 then --Start
            cooldown = 20;
            print(string.format("X,Y,Z = {%.4f, %.4f, %.4f}", xpos, ypos, zpos));
            print(string.format("yaw,pitch = {%.4f, %.4f}", yaw, pitch));
            print(string.format("pitch offset = %.4f", pitchoffs));
        end
    end
    
    
    --Ugly duplicated code for keyboard input
    keys = input.get()
    scalemult = 1;
    if keys[scaleMultDownKey] then
        scalemult = 0.1;
    elseif keys[scaleMultUpKey] then
        scalemult = 10;
    end
    if keys[movScaleUpKey] and cooldown==0 then --Dpad
        cooldown = 10;
        movscale = movscale + (0.1 * scalemult);
        print("Movement scale: " .. movscale);
    end
    if keys[movScaleDownKey] and cooldown==0 then --Dpad
        cooldown = 10;
        movscale = movscale - (0.1 * scalemult);
        print("Movement scale: " .. movscale);
    end
    if keys[rotScaleUpKey] and cooldown==0 then --Dpad
        cooldown = 10;
        rotscale = rotscale + (0.1 * scalemult);
        print("Rotation scale: " .. rotscale);
    end
    if keys[rotScaleDownKey] and cooldown==0 then --Dpad
        cooldown = 10;
        rotscale = rotscale - (0.1 * scalemult);
        print("Rotation scale: " .. rotscale);
    end
    
    if keys[forwardsKey] then
        MoveForward(-1 * movscale);
    end
    if keys[backwardsKey] then
        MoveForward(1 * movscale);
    end
    if keys[leftKey] then
        MoveRight(1 * movscale);
    end
    if keys[rightKey] then
        MoveRight(-1 * movscale);
    end
    if keys[upKey] then
        MoveUp(1 * movscale);
    end
    if keys[downKey] then
        MoveUp(-1 * movscale);
    end
    if keys[rotateUpKey] then
        pitch = (pitch - 1 * rotscale) % 360;
    end
    if keys[rotateDownKey] then
        pitch = (pitch + 1 * rotscale) % 360;
    end
    if keys[rotateLeftKey] then
        yaw = (yaw - 1 * rotscale) % 360;
    end
    if keys[rotateRightKey] then
        yaw = (yaw + 1 * rotscale) % 360;
    end
    if keys[pitchOffsetKey] then
        pitchoffs = -pitch;
    end
    if keys[resetKey] and cooldown==0 then
        cooldown = 20;
        yaw = 0.0;
        pitch = 0.0;
        pitchoffs = 0.0;
        xpos = 0.0;
        ypos = 0.0;
        zpos = 0.0;
        movscale = 1.0;
        rotscale = 1.0;
        print("Reset everything");
    end
    
    --Mouse aiming
    if keys[mouseAimButton] then
        if keys.xmouse and keys.ymouse then
            tmpX = (keys.xmouse - 128) / 128;
            tmpY = (keys.ymouse - 96) / 96;
            yaw = (yaw + tmpX * rotscale) % 360;
            pitch = (pitch + tmpY * rotscale) % 360;
        end
    end
    
    
    local tmp = Rotate(-yaw, 0.0, math.cos(math.rad(pitchoffs)), math.sin(math.rad(pitchoffs))); --roll correction magic sauce
    tmp = MatrixMult4x4(tmp, Rotate(-pitch, 1.0, 0.0, 0.0));
    local translation = {1,0,0,0,0,1,0,0,0,0,1,0,xpos,ypos,zpos,1};
    matAdjustment = MatrixMult4x4(translation, tmp);
    
    if cooldown>0 then cooldown=cooldown-1 end
    emu.frameadvance();
end

#9 Re: General » Metroid Prime Hunters LUA script » 2021-12-29 18:54:42

EddieEddie90 wrote:

Somebody please help me, I can't for the life of me get this working, as soon as I start the lua script the controls go crazy, the camera starts spinning in circles and I can't do anything. I'm using the latest desmume and the dll files are there, i'm using DS4 Windows and a ps4 controller which is recognized as a xbox 360 controller. I'm really not sure what im doing wrong and I have honestly spent most of this day looking into this to no avail. Please help sad

Try adjusting the joyid number at the top of the script and see if that helps. It accepts values 0 to 15.

#10 Re: General » Metroid Prime Hunters LUA script » 2021-10-14 16:58:35

This version of the script should work with US Rev 1. I have not extensively tested it, though.

joyid = 0 --change this if your preferred controller isn't being picked up
deadzone = 0.15 --Adjust analog stick deadzones

cooldown=0
weapon=0
subweapon=0
togglestylus=false
toggleaim=false
vcurx=128
vcury=96
key = {}
joy = {}

--Set the controls here
morphkey = "joy.down" --morph ball switch
visorkey = "joy.up" --scan visor switch(hold)
okkey = "joy.2" --ok button in messages
leftkey = "joy.left" --left arrow in messages
rightkey = "joy.right" --right arrow in messages
weaponkey = "joy.4" --weapon cycle
subweaponkey1 = "joy.5" --subweapon cycle back
subweaponkey2 = "joy.6" --subweapon cycle forward
leftstickx = "joy.x" --must be analog stick axis
leftsticky = "joy.y" --must be analog stick axis
rightstickx = "joy.u" --must be analog stick axis
rightsticky = "joy.r" --must be analog stick axis
shootkey = "joy.z" --may be either analog axis or button
boostkey = "joy.3" --morph ball boost
vstyluskey = "joy.7" --virtual stylus(hold)
jumpkey = "joy.1" --jump
startkey = "joy.8" --start


--https://www.lua.org/pil/14.1.html
function getfield (f)
  local v = _G    -- start with the table of globals
  for w in string.gfind(f, "[%w_]+") do
    v = v[w]
  end
  return v
end

while true do
    joysend = {}
    key=input.get()
    joy = controller.get(joyid)
    
    if getfield(vstyluskey) then
        toggleaim = true --this exists to just delay the pressing of the screen when you release the virtual stylus key
        if vcury>0 then
            gui.drawbox(vcurx-1, vcury-1, vcurx+1, vcury+1, "#000000", "#FFFFFF")
        else
            gui.drawbox(vcurx-1, vcury, vcurx+1, vcury+1, "#FFFFFF") --workaround for vcury=0, as it would draw to the top screen
            gui.pixel(vcurx, vcury, "#000000")
        end
        
        if getfield(subweaponkey2) then
            stylus.set{x=vcurx, y=vcury, touch=true}
        end
        if getfield(subweaponkey1) and cooldown==0 then
            togglestylus = not togglestylus
            print('Touchscreen input allowed = ' .. tostring(togglestylus))
            cooldown = 20
        end
        tmp = getfield(rightstickx)
        if tmp and math.abs(tmp)>deadzone then
            vcurx = vcurx + (tmp * 5)
        end
        tmp = getfield(rightsticky)
        if tmp and math.abs(tmp)>deadzone then
            vcury = vcury + (tmp * 5)
        end
        
        if vcurx<0 then vcurx=0 end
        if vcurx>255 then vcurx=255 end
        if vcury<0 then vcury=0 end
        if vcury>191 then vcury=191 end
    else
        if getfield(morphkey) then --Morph
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=231, y=167, touch=true} emu.frameadvance() emu.frameadvance()
        end
        if getfield(visorkey) then --Visor
            if cooldown == 0 then
                stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            end
            cooldown = 10
            stylus.set{x=128, y=173, touch=true}
        end
        if getfield(okkey) then --OK (in scans and messages)
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=128, y=142, touch=true} emu.frameadvance() emu.frameadvance()
        end
        if getfield(leftkey) then --Left arrow (in scans and messages)
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=71, y=141, touch=true} emu.frameadvance() emu.frameadvance()
        end
        if getfield(rightkey) then --Right arrow (in scans and messages)
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=185, y=141, touch=true} emu.frameadvance() emu.frameadvance()
        end
        if getfield(weaponkey) and cooldown==0 then --Switch weapon (beam->missile->subweapon->beam)
            cooldown=20
            weapon=(weapon+1)%3
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=85+40*weapon, y=32, touch=true} emu.frameadvance()
            stylus.set{x=85+40*weapon, y=32, touch=true}
        end
        if (getfield(subweaponkey1) or getfield(subweaponkey2)) and cooldown==0 then --Switch subweapon (previous and next)
            cooldown=20
            weapon=2
            if getfield(subweaponkey1) then subweapon=(subweapon-1)%6 end --previous
            if getfield(subweaponkey2) then subweapon=(subweapon+1)%6 end --next
            subX=93+25*subweapon subY=48+25*subweapon
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=232, y=34, touch=true} emu.frameadvance()
            stylus.set{x=232, y=34, touch=true} emu.frameadvance()
            stylus.set{x=subX, y=subY, touch=true} emu.frameadvance()
            stylus.set{x=subX, y=subY, touch=true}
        end
        tmp = getfield(leftstickx)
        if tmp and tmp < -deadzone then
            joysend.left = true
        elseif tmp and tmp > deadzone then
            joysend.right = true
        end
        tmp = getfield(leftsticky)
        if tmp and tmp < -deadzone then
            joysend.up = true
        elseif tmp and tmp > deadzone then
            joysend.down = true
        end
        tmp = getfield(rightstickx)
        if tmp and math.abs(tmp)>deadzone and togglestylus==false then
            memory.writedword(0x020DEDA6, (tmp * 4))
            toggleaim=false
        end
        tmp = getfield(rightsticky)
        if tmp and math.abs(tmp)>deadzone and togglestylus==false then
            memory.writedword(0x020DEDAE, (tmp * 6))
            toggleaim=false
        end
        if getfield(boostkey) then
            joysend.R = true
        end
        
        tmp = getfield(shootkey)
        if tmp then
            if type(tmp) == "number" then
                if math.abs(tmp) > deadzone then
                    joysend.L = true
                end
            elseif type(tmp) == "boolean" then
                joysend.L = true
            end
        end
        
        if getfield(jumpkey) then
            joysend.B = true
        end
        if getfield(startkey) then
            joysend.start = true
        end
    end
    
    if cooldown>0 then
        cooldown=cooldown-1
    else
        ball = memory.readbyte(0x020DB098) == 0x02 --Is this a good way of detecting morph ball status??
        if ball==false and togglestylus==false and toggleaim==false then
            stylus.set{x=128, y=96, touch=true} --Required for aiming
        end
    end
    
    joypad.set(1,joysend)
    emu.frameadvance()
end

#11 Re: General » Metroid Prime Hunters LUA script » 2021-10-14 00:22:54

What region game are you using? This was only tested on the US version. It has hardcoded memory addresses that it injects values at that probably won't work with other regions.


EDIT: Specifically the first release, not Rev 1. The working ROM has the SHA1 hash 90164D1AC127EE5F9815EA4AE7DE798C7B5FC629

#13 Re: General » Metroid Prime Hunters LUA script » 2021-04-06 02:26:32

Also, it looks like input in Lua was broken for the last month in DeSmuME builds, but is now fixed. If you get an error when trying to run one of the scripts then make sure you have an up-to-date build of the emulator.

#14 Re: General » Metroid Prime Hunters LUA script » 2021-04-05 19:49:14

Legacy wrote:

I'm actually new to adjusting scripts in DesMume and I'm a visual learner, so I was wondering if you could post of video of how to do this? (I'm sure you would get a lot of views on youtube as well considering no one has done this.)
I always wanted to play Metroid prime hunters but trying to use my right hand for my laptop's mouse and my left hand for the controller is a horrible gaming experience.

Thank you in advance for your time!

What are you having trouble with?

To run Lua scripts you will need the correct dlls in the folder with your DeSmuME exe. A Lua setup guide copied from here:

Download the Lua DLL that matches your Desmume: - https://sourceforge.net/projects/luabin … s/Dynamic/

lua-5.1.5_Win32_dll14_lib.zip for x86 Desmume
lua-5.1.5_Win64_dll14_lib.zip for x86-64 Desmume

Extract lua5.1.dll from the .zip file to the same folder where your DeSmuME_0.9.11_x86.exe or DeSmuME_0.9.11_x64.exe is

Rename lua5.1.dll to lua51.dll

Copy the script from my post into a text file and rename the extension to ".lua". In DeSmuME once the game is running go to "Tools -> Lua Scripting -> New Lua Script Window...". In the new window that pops up hit browse and select the script you saved and it will start running.


If you want to adjust the controls then use this lua script to identify your controller and its buttons

dpad = {"up", "down", "left", "right"}
sticks = {"x", "y", "z", "r", "u", "v"}

function showControllers()
    gui.box(0, 0, 256, 192, "#808080")
    gui.box(0, -192, 256, 0, "#808080")
    for i=0,15 do
        cont = controller.get(i)
        if type(next(cont)) ~= "nil" then
            gui.text(0, (18*i)-143, i)
            xoffs = 14
            for ii=0,32 do
                col = "#606060"
                tmp = tostring(ii)
                if cont[tmp] ~= nil then
                    if cont[tmp] == true then col = "#FF0000" end
                    gui.text(xoffs, (18*i)-143, tmp, col) --goes off screen if controller has more than ~20 buttons.
                    xoffs = xoffs + string.len(tmp)*6 + 2
                end
            end
            xoffs = 14
            for idx, name in ipairs(dpad) do
                col = "#606060"
                if cont[name] ~= nil then
                    if cont[name] == true then col = "#FF0000" end
                    gui.text(xoffs, (18*i)-143+9, name, col)
                    xoffs = xoffs + string.len(name)*6 + 4
                end
            end
            for idx, name in ipairs(sticks) do
                col = "#606060"
                if cont[name] ~= nil then
                    if math.abs(cont[name]) > 0.25 then col = "#FF0000" end
                    gui.text(xoffs, (18*i)-143+9, name, col)
                    xoffs = xoffs + string.len(name)*6 + 2
                end
            end
        end
    end
end
gui.register(showControllers)

CzzpAOV.png
You should see output similar to this, but different depending on your controller and how many of them you have hooked up.
The white number on the left is the controller id. When you press a button it will turn red to help you identify which button corresponds to which number.
In the main script you can then adjust joyid and the controls with the values you find here.

#15 Re: General » Metroid Prime Hunters LUA script » 2021-04-02 01:45:01

Updated the script to include a "virtual stylus" for navigating stuff like your ship and the main menu. When holding select(on an xbox style controller) a virtual stylus will be displayed. It can be moved with the right stick and touch the screen with the right bumper. You can still toggle manual touchscreen input if needed with select + left bumper.

#16 General » Metroid Prime Hunters LUA script » 2021-03-30 04:32:33

windwakr
Replies: 17

I've been sitting on this for a while(years) now and decided to clean it up a bit and post it. This script allows you to play Metroid Prime hunters with a controller with smooth camera movement.
Some small snippets(like subweapon switching) were borrowed from https://forums.desmume.org/viewtopic.php?id=10957 .
This may only work on the Windows version of DeSmuME, I'm not sure.

This requires the US ROM with a SHA1 hash of 90164D1AC127EE5F9815EA4AE7DE798C7B5FC629

Clear any controller input you may have set up in DeSmuME before using.
By default it's set up for an Xbox style controller but the controls are easily customizable.

left stick - move
right stick - aim
left or right trigger - shoot/scan
bumpers - cycle subweapon forward/back
start - start
d-pad up(hold) - scan visor
d-pad down - morphball
d-pad left - presses back arrow on messages
d-pad right - presses forward arrow on messages
B - presses OK on messages
A - jump
X - morphball boost
Y - cycle weapon(beam/missile/subweapon)
select(hold) - virtual stylus

while virtual stylus is active
  right stick - move the virtual stylus
  right bumper - tap the screen
  left bumper - toggle manual touchscreen input

The way aiming works is the center of the screen is continually pressed and the script injects stylus movement into the game's memory. If you need to manually use the touchscreen then use select+left bumper to toggle aiming on or off. That shouldn't be necessary with the existence of the virtual stylus, though.

Use with the U.S. version of the game.

joyid = 0 --change this if your preferred controller isn't being picked up
deadzone = 0.15 --Adjust analog stick deadzones

cooldown=0
weapon=0
subweapon=0
togglestylus=false
toggleaim=false
vcurx=128
vcury=96
key = {}
joy = {}

--Set the controls here
morphkey = "joy.down" --morph ball switch
visorkey = "joy.up" --scan visor switch(hold)
okkey = "joy.2" --ok button in messages
leftkey = "joy.left" --left arrow in messages
rightkey = "joy.right" --right arrow in messages
weaponkey = "joy.4" --weapon cycle
subweaponkey1 = "joy.5" --subweapon cycle back
subweaponkey2 = "joy.6" --subweapon cycle forward
leftstickx = "joy.x" --must be analog stick axis
leftsticky = "joy.y" --must be analog stick axis
rightstickx = "joy.u" --must be analog stick axis
rightsticky = "joy.r" --must be analog stick axis
shootkey = "joy.z" --may be either analog axis or button
boostkey = "joy.3" --morph ball boost
vstyluskey = "joy.7" --virtual stylus(hold)
jumpkey = "joy.1" --jump
startkey = "joy.8" --start


--https://www.lua.org/pil/14.1.html
function getfield (f)
  local v = _G    -- start with the table of globals
  for w in string.gfind(f, "[%w_]+") do
    v = v[w]
  end
  return v
end

while true do
    joysend = {}
    key=input.get()
    joy = controller.get(joyid)
    
    if getfield(vstyluskey) then
        toggleaim = true --this exists to just delay the pressing of the screen when you release the virtual stylus key
        if vcury>0 then
            gui.drawbox(vcurx-1, vcury-1, vcurx+1, vcury+1, "#000000", "#FFFFFF")
        else
            gui.drawbox(vcurx-1, vcury, vcurx+1, vcury+1, "#FFFFFF") --workaround for vcury=0, as it would draw to the top screen
            gui.pixel(vcurx, vcury, "#000000")
        end
        
        if getfield(subweaponkey2) then
            stylus.set{x=vcurx, y=vcury, touch=true}
        end
        if getfield(subweaponkey1) and cooldown==0 then
            togglestylus = not togglestylus
            print('Touchscreen input allowed = ' .. tostring(togglestylus))
            cooldown = 20
        end
        tmp = getfield(rightstickx)
        if tmp and math.abs(tmp)>deadzone then
            vcurx = vcurx + (tmp * 5)
        end
        tmp = getfield(rightsticky)
        if tmp and math.abs(tmp)>deadzone then
            vcury = vcury + (tmp * 5)
        end
        
        if vcurx<0 then vcurx=0 end
        if vcurx>255 then vcurx=255 end
        if vcury<0 then vcury=0 end
        if vcury>191 then vcury=191 end
    else
        if getfield(morphkey) then --Morph
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=231, y=167, touch=true} emu.frameadvance() emu.frameadvance()
        end
        if getfield(visorkey) then --Visor
            if cooldown == 0 then
                stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            end
            cooldown = 10
            stylus.set{x=128, y=173, touch=true}
        end
        if getfield(okkey) then --OK (in scans and messages)
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=128, y=142, touch=true} emu.frameadvance() emu.frameadvance()
        end
        if getfield(leftkey) then --Left arrow (in scans and messages)
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=71, y=141, touch=true} emu.frameadvance() emu.frameadvance()
        end
        if getfield(rightkey) then --Right arrow (in scans and messages)
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=185, y=141, touch=true} emu.frameadvance() emu.frameadvance()
        end
        if getfield(weaponkey) and cooldown==0 then --Switch weapon (beam->missile->subweapon->beam)
            cooldown=20
            weapon=(weapon+1)%3
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=85+40*weapon, y=32, touch=true} emu.frameadvance()
            stylus.set{x=85+40*weapon, y=32, touch=true}
        end
        if (getfield(subweaponkey1) or getfield(subweaponkey2)) and cooldown==0 then --Switch subweapon (previous and next)
            cooldown=20
            weapon=2
            if getfield(subweaponkey1) then subweapon=(subweapon-1)%6 end --previous
            if getfield(subweaponkey2) then subweapon=(subweapon+1)%6 end --next
            subX=93+25*subweapon subY=48+25*subweapon
            stylus.set{touch=false} emu.frameadvance() emu.frameadvance()
            stylus.set{x=232, y=34, touch=true} emu.frameadvance()
            stylus.set{x=232, y=34, touch=true} emu.frameadvance()
            stylus.set{x=subX, y=subY, touch=true} emu.frameadvance()
            stylus.set{x=subX, y=subY, touch=true}
        end
        tmp = getfield(leftstickx)
        if tmp and tmp < -deadzone then
            joysend.left = true
        elseif tmp and tmp > deadzone then
            joysend.right = true
        end
        tmp = getfield(leftsticky)
        if tmp and tmp < -deadzone then
            joysend.up = true
        elseif tmp and tmp > deadzone then
            joysend.down = true
        end
        tmp = getfield(rightstickx)
        if tmp and math.abs(tmp)>deadzone and togglestylus==false then
            memory.writedword(0x020DE526, (tmp * 4))
            toggleaim=false
        end
        tmp = getfield(rightsticky)
        if tmp and math.abs(tmp)>deadzone and togglestylus==false then
            memory.writedword(0x020DE52E, (tmp * 6))
            toggleaim=false
        end
        if getfield(boostkey) then
            joysend.R = true
        end
        
        tmp = getfield(shootkey)
        if tmp then
            if type(tmp) == "number" then
                if math.abs(tmp) > deadzone then
                    joysend.L = true
                end
            elseif type(tmp) == "boolean" then
                joysend.L = true
            end
        end
        
        if getfield(jumpkey) then
            joysend.B = true
        end
        if getfield(startkey) then
            joysend.start = true
        end
    end
    
    if cooldown>0 then
        cooldown=cooldown-1
    else
        ball = memory.readbyte(0x020DA818) == 0x02 --Is this a good way of detecting morph ball status??
        if ball==false and togglestylus==false and toggleaim==false then
            stylus.set{x=128, y=96, touch=true} --Required for aiming
        end
    end
    
    joypad.set(1,joysend)
    emu.frameadvance()
end

video recorded while using this script: https://files.catbox.moe/o6r3r5.webm

#17 Re: General » freelook Lua script » 2019-04-21 18:30:43

Bnthomason wrote:

So, how would I reverse or change the controller assignments?

For example, in some games, when you push the L or R stick right, the camera pans in the opposite direction.

If both the up/down and left/right of the stick are inverted you can try using the dpad to set a negative scale. If you only need one axis inverted then you're on your own. The code's quite ugly but there are comments by the bits of code handling each button/stick. You should be able to slap(or remove) a '-' in there somewhere to invert the axis you need.


edit:
For example, to invert just the left/right of the left stick you'd change "MoveRight(-key.x * movscale);" to "MoveRight(key.x * movscale);". For up/down on the right stick you'd change "pitch = (pitch + (key.r * rotscale)) % 360;" to "pitch = (pitch + (-key.r * rotscale)) % 360;"

#18 Re: General » freelook Lua script » 2019-04-19 23:04:32

Flyhngon wrote:

Total coding noob here... I'm trying wrap my head around how to use lua code, and I can't wrap my head around how to get the freelook script to work. I get that I actually have to adjust some things in the code myself, but I'm unclear about what values need changing, and no matter what I do the code stops running because register3devent is a nil value..? Even when I think I'm defining it correctly?

Freecam is a feature I'd really like, but I'm too tech-illiterate to wrap my head around how the lua script works, even with the instructions given on the github and in the script itself.. help?

You need a recent-ish build of the emulator.

Bnthomason wrote:

Hey, what emulator build are you using, and how would I get this to work with a 9.12 emulator with decent speed and graphics?

https://desmume.org/download/
Scroll down to "Official Nightly Builds"

#19 Re: General » freelook Lua script » 2019-03-08 21:41:27

Got a new controller recently and was messing around with this a bit. Updated the first post but here's the changelog.
*Now attempts to scan for a suitable controller instead of just using the first one
*Only updates the matrix on 3D events(still don't think I'm doing it properly, but this fixes most of the issues I had)
*Left and right bumpers now act as multipliers for the dpad scale settings
*Y button resets movement and rotation scale to 1.0

#20 Re: General » freelook Lua script » 2018-11-01 01:38:47

zeromus wrote:

It looks like we need your fork. Which is now AWOL.

Even if it's sloppy, paste your code onto our master and send a PR and I'll make sure it's OK
also I too was missing math.abs.. I have no idea why (don't want to know; I hate lua) but anyway here is a math.abs:

math.abs = function(x)
  if x == nil then return 0 end
  if x<0 then return -x end
  return x;
end

My fork was just what the queefersutherland guy PR'd, I don't think he made any changes to it. I didn't want to make a PR myself. The script just assumes you have a controller plugged in that's joystick id 0 and has the 3 axes it checks for, and that causes an error when trying to use them if they don't exist. It really should check for the axes before trying to use them.

#21 General » freelook Lua script » 2018-10-12 17:32:49

windwakr
Replies: 22

EDIT: A version with keyboard controls and mouse aiming is available here

Putting this here to get people to stop replying in the issue page on github.

DeSmuME now has the ability to implement freelook through Lua scripting. Read zeromus' explanation on that here.

I've created this crappy script to look around with a controller. Honestly have no clue what I'm doing, but it somehow works well enough with most games I've tried. It's not a universal solution, though. There's a bunch of games that it doesn't work that great or at all with.

Some webms showing it off:
https://files.catbox.moe/zfni2x.webm
https://files.catbox.moe/0fn3dt.webm
https://files.catbox.moe/s8hev7.webm

Use with something like an xbox 360 controller, or modify the controls to suit your needs.

Left stick moves left/right and forward/back
Right stick rotates
Triggers move up down(according to where you're looking)

A button resets position, rotation, and pitch offset
B button resets rotation and pitch offset
X sets pitch offset
Y resets the movement and rotation scale

Dpad Up/Down sets the movement scale
Dpad Left/Right sets the rotation scale
Hold the left or right bumper to make smaller or bigger changes to the scales

Setting the pitch offset is important. Without doing so, your view will roll around as you look left and right in most games.
Angle your pitch towards the "horizon" and press X. Then try and rotate the camera 90 degrees to the left or right and adjust up/down a little while pressing X until everything's level.

--Assumes you have an Xbox style controller

joyID = -1; --Joystick ID to use. The script will attempt to find a suitable joystick, but you can manually set this if you wish
yaw = 0.0;
pitch = 0.0;
pitchoffs = 0.0; --used to correct for the initial pitch of the camera
xpos = 0.0; --coordinates in a virtual 3D space
ypos = 0.0; --^^^
zpos = 0.0; --^^^
matAdjustment = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};
movscale = 1.0;
rotscale = 1.0;
cooldown = 0;

function MatrixMult4x4(m1, m2) --sorta copied/inspired from some lua matrix library
    local mtx = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    for i=1,4 do
        for j=1,4 do
            local num = 0
            for n=1,4 do
                num = num + m1[(i-1)*4+n] * m2[(n-1)*4+j]
            mtx[(i-1)*4+j] = num
            end
        end
    end
    return mtx;
end

function Rotate(angle,x,y,z)
    local s = math.sin(math.rad(angle));
    local c = math.cos(math.rad(angle));
    return {(1-c)*x*x+c, (1-c)*y*x-s*z, (1-c)*z*x+s*y, 0,
            (1-c)*x*y+s*z, (1-c)*y*y+c, (1-c)*z*y-s*x, 0,
            (1-c)*x*z-s*y, (1-c)*y*z+s*x, (1-c)*z*z+c, 0,
            0, 0, 0, 1};
end

function vecrotx(angle, vx, vy, vz) --used for pitch correction in the functions below
    return vx,
            vy * math.cos(math.rad(angle)) - vz * math.sin(math.rad(angle)),
            vy * math.sin(math.rad(angle)) + vz * math.cos(math.rad(angle));
end

function MoveForward(l) --sorta based off https://www.gamedev.net/forums/topic/415144-get-forward-right-up-vectors-from-pitch-yaw-roll/
    local fX = math.sin(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs));
    local fY = -math.sin(math.rad(pitch+pitchoffs));
    local fZ = -math.cos(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs));
    
    fX, fY, fZ = vecrotx(pitchoffs, fX, fY, fZ);
    
    local mag = math.sqrt(fX*fX + fY*fY + fZ*fZ);
    fX = fX / mag; fY = fY / mag; fZ = fZ / mag;
    fX = fX * l; fY = fY * l; fZ = fZ * l;
    xpos = xpos + fX; ypos = ypos + fY; zpos = zpos + fZ;
end

function MoveUp(l)
    local fX = math.sin(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs+90));
    local fY = -math.sin(math.rad(pitch+pitchoffs+90));
    local fZ = -math.cos(math.rad(yaw)) * math.cos(math.rad(pitch+pitchoffs+90));
    
    fX, fY, fZ = vecrotx(pitchoffs, fX, fY, fZ);
    
    local mag = math.sqrt(fX*fX + fY*fY + fZ*fZ);
    fX = fX / mag; fY = fY / mag; fZ = fZ / mag;
    fX = fX * l; fY = fY * l; fZ = fZ * l;
    xpos = xpos + fX; ypos = ypos + fY; zpos = zpos + fZ;
end

function MoveRight(l)
    local fX = math.sin(math.rad(yaw+90));
    local fY = 0;
    local fZ = -math.cos(math.rad(yaw+90));
    
    fX, fY, fZ = vecrotx(pitchoffs, fX, fY, fZ);
    
    local mag = math.sqrt(fX*fX + fY*fY + fZ*fZ);
    fX = fX / mag; fY = fY / mag; fZ = fZ / mag;
    fX = fX * l; fY = fY * l; fZ = fZ * l;
    xpos = xpos + fX; ypos = ypos + fY; zpos = zpos + fZ;
end

function on3d()
    emu.set3dtransform(2,matAdjustment);
end
emu.register3devent(on3d);
emu.set3dtransform(1,nil);

if joyID == -1 then
    for i = 0, 15 do
        c = controller.get(i);
        if c.x and c.y and c.z and c.u and c.r then --simple check that only looks for all the needed axes
            joyID = i;
            print("Found suitable joystick, id " .. i);
            break;
        end
    end
else
    print("Using manually set joystick, id " .. joyID);
end
if joyID == -1 then
    print("No suitable joystick found!");
    while true do
        emu.frameadvance();
    end
end

while true do
    local key = controller.get(joyID)
    
    scalemult = 1;
    if key["5"] then --Left bumper
        scalemult = 0.1;
    elseif key["6"] then --Right bumper
        scalemult = 10;
    end
    if key.up and cooldown==0 then --Dpad
        cooldown = 10;
        movscale = movscale + (0.1 * scalemult);
        print("Movement scale: " .. movscale);
    end
    if key.down and cooldown==0 then --Dpad
        cooldown = 10;
        movscale = movscale - (0.1 * scalemult);
        print("Movement scale: " .. movscale);
    end
    if key.right and cooldown==0 then --Dpad
        cooldown = 10;
        rotscale = rotscale + (0.1 * scalemult);
        print("Rotation scale: " .. rotscale);
    end
    if key.left and cooldown==0 then --Dpad
        cooldown = 10;
        rotscale = rotscale - (0.1 * scalemult);
        print("Rotation scale: " .. rotscale);
    end
    
    if(math.abs(key.x)>0.25) then --Left Stick
        MoveRight(-key.x * movscale);
    end
    if(math.abs(key.y)>0.25) then --Left Stick
        MoveForward(key.y * movscale);
    end
    if(math.abs(key.z)>0.25) then --Triggers
        MoveUp(-key.z * movscale);
    end
    if(math.abs(key.u)>0.25) then --Right Stick
        yaw = (yaw + (key.u * rotscale)) % 360;
    end
    if(math.abs(key.r)>0.25) then --Right Stick
        pitch = (pitch + (key.r * rotscale)) % 360;
    end
    
    if key["1"] and cooldown==0 then --A button
        cooldown = 20;
        yaw = 0.0;
        pitch = 0.0;
        pitchoffs = 0.0;
        xpos = 0.0;
        ypos = 0.0;
        zpos = 0.0;
        movscale = 1.0;
        rotscale = 1.0;
        print("Reset everything");
    end
    if key["2"] and cooldown==0 then --B button
        cooldown = 20;
        yaw = 0.0;
        pitch = 0.0;
        pitchoffs = 0.0;
        print("Reset rotations");
    end
    if key["3"] then --X button
        pitchoffs = -pitch;
    end
    if key["4"] and cooldown==0 then --Y button
        cooldown = 20;
        movscale = 1.0;
        rotscale = 1.0;
        print("Movement/rotation scales reset");
    end
    
    local tmp = Rotate(-yaw, 0.0, math.cos(math.rad(pitchoffs)), math.sin(math.rad(pitchoffs)));
    tmp = MatrixMult4x4(tmp, Rotate(-pitch, 1.0, 0.0, 0.0));
    local translation = {1,0,0,0,0,1,0,0,0,0,1,0,xpos,ypos,zpos,1};
    matAdjustment = MatrixMult4x4(translation, tmp);
    
    if cooldown>0 then cooldown=cooldown-1 end
    emu.frameadvance();
end

#22 Re: Support » 3d Picross has jitter / shaky textures » 2017-03-02 02:43:35

I've come up with a simpler code. I hadn't actually looked into the game's disassembly when I made that code earlier. I found the values being set in no$gba's i/o map viewer and searched for them in memory, then made the code to zero them. Turns out the game has support to just set those values to zero if a certain read value is zero.

Picross 3D (USA):

0208B5B0 E3A00000

Picross 3D (Europe):

0208B4DC E3A00000

Rittai Picross (Japan):

020A3BBC E3A00000

#23 Re: Support » 3d Picross has jitter / shaky textures » 2017-03-01 22:42:51

The game alters part of the texture matrix on alternating frames. You can disable this with this AR code:

-- see below post --

#24 Re: Compatibility » Umihara Kawase Shun - slow emulation » 2016-06-30 04:43:52

rogerman wrote:

SoftRasterizer easily makes this game playable.

Not really(well, maybe if you have the latest Intel® processor). Like I've already mentioned, for me the software renderer gets the same speed as OpenGL without scaling(~30fps) once I move around the level a bit. The only place where it's running at a playable speed is the very start of the level.

Guess I'll just dig my physical copy and play on the real thing.

#25 Compatibility » Umihara Kawase Shun - slow emulation » 2016-06-29 23:13:29

windwakr
Replies: 4

This game seems to be doing something with its graphics to cause some severe slowdown. In either the software renderer or the OpenGL renderer, it runs at half speed for me when I start moving around. With the OpenGL renderer and texture scaling set to 2x, the FPS drops down to below 5(see https://i.imgur.com/01TeL0A.png). Seems odd for such a simple looking game. No other games drop to below 55FPS for me with the same settings.

Board footer

Powered by FluxBB